线性规划与网络流 24 题 题解

网络流模板

笔者最大流使用 Dinic 算法,费用流使用 SPFA 算法。在网络流问题中,若需对一个图反复求最大流,则每次求最大流前需将当前可行流清零。在费用流问题中,当所求为最大费用流时,可以将所有边费用取负,等价地转化为最小费用流问题。原题题目描述略,由于无 SPJ,故所有输出方案类询问均予以忽略,必要时转化为判定性问题。代码如下:

#include
#define MAX_E (0xFFFFF)
#define MAX_V (0xFFFF)
#define INF (0x7FFFFFFF)
#define Min_Integer(a,b) \
({ \
    int __tmp_a=(a),__tmp_b=(b); \
    __tmp_a<__tmp_b?__tmp_a:__tmp_b; \
})
struct MaxFlow
{
    int cur[MAX_V],dist[MAX_V],e,que[MAX_V],s,t,v,vertex[MAX_V];
    struct MaxFlow_Edge
    {
        int cap,flow,next,to;
    }edge[MAX_E];
    MaxFlow()
    {
        e=0;
        memset(vertex,-1,sizeof(vertex));
    }
    void AddSingleEdge(int eg,int fr,int to,int cp)
    {
        edge[eg].to=to,edge[eg].cap=cp,edge[eg].flow=0;
        edge[eg].next=vertex[fr],vertex[fr]=eg;
    }
    void AddEdge(int u,int v,int c)
    {
        AddSingleEdge(e<<1,u,v,c);
        AddSingleEdge(e<<1^1,v,u,0);
        e++;
    }
    bool Dinic_Bfs()
    {
        int QueueTop=0;
        memset(dist,-1,sizeof(dist));
        dist[s]=0;
        que[QueueTop++]=s;
        for(int i=0;ifor(int j=vertex[que[i]];j>-1;j=edge[j].next)
                if(dist[edge[j].to]==-1&&edge[j].cap>edge[j].flow)
                    dist[edge[j].to]=dist[que[i]]+1,
                    que[QueueTop++]=edge[j].to;
        return dist[t]>-1;
    }
    int Dinic_Dfs(int now,int flow)
    {
        if(now==t)
            return flow;
        int f,sum=0;
        for(;cur[now]>-1&&flow>0;cur[now]=edge[cur[now]].next)
            if(dist[now]+1==dist[edge[cur[now]].to]
                &&(f=Dinic_Dfs(edge[cur[now]].to,Min_Integer(flow,edge[cur[now]].cap-edge[cur[now]].flow)))>0)
                edge[cur[now]].flow+=f,
                edge[cur[now]^1].flow-=f,
                sum+=f,
                flow-=f;
        return sum;
    }
    int Solve()
    {
        int ans=0;
        while(Dinic_Bfs())
        {
            for(int i=1;i<=v;i++)
                cur[i]=vertex[i];
            ans+=Dinic_Dfs(s,INF);
        }
        return ans;
    }
};
struct MinCost
{
    bool inq[MAX_V];
    int dist[MAX_V],e,flow[MAX_V],prev[MAX_V],que[MAX_V],s,t,v,vertex[MAX_V];
    struct MinCost_Edge
    {
        int cap,cost,flow,from,next,to;
    }edge[MAX_E];
    MinCost()
    {
        e=0;
        memset(vertex,-1,sizeof(vertex));
    }
    void AddSingleEdge(int eg,int fr,int to,int cp,int ct)
    {
        edge[eg].from=fr,edge[eg].to=to,edge[eg].cap=cp,edge[eg].flow=0,edge[eg].cost=ct;
        edge[eg].next=vertex[fr],vertex[fr]=eg;
    }
    void AddEdge(int u,int v,int c,int t)
    {
        AddSingleEdge(e<<1,u,v,c,t);
        AddSingleEdge(e<<1^1,v,u,0,-t);
        e++;
    }
    void ClearEdgeFlow()
    {
        for(int i=0;i1;i++)
            edge[i].flow=0;
    }
    void NegateEdgeCost()
    {
        for(int i=0;i1;i++)
            edge[i].cost=-edge[i].cost;
    }
    bool Bfs(int &mf,int &mc)
    {
        int QueueTop=0;
        for(int i=1;i<=v;i++)
            dist[i]=INF,flow[i]=0,inq[i]=false,prev[i]=-1;
        dist[s]=0;
        flow[s]=INF;
        inq[s]=false;
        que[QueueTop++]=s;
        for(int i=0;iq[que[i]]=false;
            for(int j=vertex[que[i]];j>-1;j=edge[j].next)
                if(edge[j].cap>edge[j].flow&&dist[que[i]]+edge[j].costif(!inq[edge[j].to])
                        inq[edge[j].to]=true,
                        que[QueueTop++]=edge[j].to;
                }
        }
        if(dist[t]==INF)
            return false;
        for(int i=t;i!=s;i=edge[prev[i]].from)
            edge[prev[i]].flow+=flow[t],
            edge[prev[i]^1].flow-=flow[t];
        mf+=flow[t],mc+=dist[t]*flow[t];
        return true;
    }
    void Solve(int &mf,int &mc)
    {
        while(Bfs(mf,mc));
    }
};

第一题 飞行员配对方案问题

最大流。将飞行员看做点,可配对的飞行员之间看做边,则原题为二分图最大匹配问题。建图如下:
1. 对于每对可配对的英国飞行员和外籍飞行员,从英国飞行员点向外籍飞行员点连流量 INF 的边;
2. 建总源点 S,从 S 向每个外籍飞行员点连流量 1 的边;
3. 建总汇点 T,从每个英国飞行员点向 T 连流量 1 的边。
该图最大流的值即为答案。代码如下:

#include
MaxFlow net;
int main()
{
    int m,n,u,v;
    scanf("%d %d",&m,&n);
    while(true)
    {
        scanf("%d %d",&u,&v);
        if(u==-1&&v==-1)
            break;
        net.AddEdge(u,v,INF);
    }
    net.s=n+1,net.t=net.v=n+2;
    for(int i=1;i<=m;i++)
        net.AddEdge(net.s,i,1);
    for(int i=m+1;i<=n;i++)
        net.AddEdge(i,net.t,1);
    printf("%d",net.Solve());
    return 0;
}

第二题 太空飞行计划问题

最小割。建图如下:
1. 建总源点 S,从 S 向每个实验点连流量为实验费用的边;
2. 从每个实验点向该实验所需仪器点连流量 INF 的边;
3. 建总汇点 T,从每个仪器点向 T 连流量为仪器费用的边。
所有实验总费用减去该图最大流的值即为答案。代码如下:

#include
MaxFlow net;
int main()
{
    char t;
    int c,m,n,s=0;
    scanf("%d %d",&m,&n);
    net.s=m+n+1,net.t=net.v=net.s+1;
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&c);
        s+=c;
        net.AddEdge(net.s,i,c);
        while((t=getchar())!='\n')
            scanf("%d",&c),
            net.AddEdge(i,m+c,INF);
    }
    for(int i=1;i<=n;i++)
        scanf("%d",&c),
        net.AddEdge(m+i,net.t,c);
    printf("%d",s-net.Solve());
    return 0;
}

第三题 最小路径覆盖问题

最大流。在一个有向无环图的路径覆盖方案中,每个点的入边和出边都不大于一条。拆点,使尽可能多的入点和出点两两对应,则原题转化为二分图的最大匹配问题。建图如下:
1. 拆点,将原图中每个点拆成入点和出点;
2. 对于原图中每条有向边,从该边起点的入点向该边终点的出点连流量 INF 的边;
3. 建总源点 S,从 S 向原图中每个点的入点连流量 1 的边;
4. 建总汇点 T,从原图中每个点的出点向 T 连流量 1 的边。
原图中节点个数减去该图最大流的值即为答案。代码如下:

#include
MaxFlow net;
int main()
{
    int m,n,u,v;
    scanf("%d %d",&n,&m);
    while(m--)
        scanf("%d %d",&u,&v),
        net.AddEdge(u,n+v,INF);
    net.s=(n<<1)+1,net.t=net.v=net.s+1;
    for(int i=1;i<=n;i++)
        net.AddEdge(net.s,i,1),
        net.AddEdge(i+n,net.t,1);
    printf("%d",n-net.Solve());
    return 0;
}

第四题 魔术球问题

最大流。从小到大枚举可放球数,将原题转化为判定性问题。把球看作点,可相邻的球间看作由编号小球向编号大球的有向边,则原题转化为最小路径覆盖问题。建图如下:
1. 拆点,将每个球点拆成入点和出点;
2. 对于每对可相邻的球,从编号小球的入点向编号大球的出点连流量 INF 的边;
3. 建总源点 S,从 S 向每个球点的入点连流量 1 的边;
4. 建总汇点 T,从每个球点的出点向 T 连流量 1 的边。
使得该图最大流不大于柱数的最大球数即为答案。代码如下:

#include
MaxFlow net;
int main()
{
    int m=0,n,s=0;
    scanf("%d",&n);
    net.s=1,net.t=net.v=2;
    do
    {
        ++m,net.v+=2;
        net.AddEdge(net.s,m<<1^1,1);
        net.AddEdge(m+1<<1,net.t,1);
        for(int i=1;i*i<m<<1;i++)
            if(i*i>m)
                net.AddEdge(i*i-m<<1^1,m+1<<1,INF);
        s+=net.Solve();
    }while(m-s<=n);
    printf("%d",--m);
    return 0;
}

第五题 圆桌问题

最大流。将单位和餐桌看作点,代表看作流量。建图如下:
1. 建总源点 S,从 S 向每个单位点连流量为该单位代表数的边;
2. 建总汇点 T,从每个餐桌点向 T 连流量为该餐桌代表数的边;
3. 对于每个单位点与每个餐桌点,从该单位点向该餐桌点连流量 1 的边。
该图最大流是否等于所有单位代表总数即为答案。代码如下:

#include
MaxFlow net;
int main()
{
    int c,m,n,s=0;
    scanf("%d %d",&m,&n);
    net.s=m+n+1,net.t=net.v=net.s+1;
    for(int i=1;i<=m;i++)
        scanf("%d",&c),s+=c,
        net.AddEdge(net.s,i,c);
    for(int i=m+1;i<=m+n;i++)
        scanf("%d",&c),
        net.AddEdge(i,net.t,c);
    for(int i=1;i<=m;i++)
        for(int j=m+1;j<=m+n;j++)
            net.AddEdge(i,j,1);
    printf("%d",net.Solve()==s?1:0);
    return 0;
}

第六题 最长递增子序列问题

最大流。先用动态规划求出以 x[i] 为结束的最长递增子序列的长度,记为 f[i],f[1 .. n] 中最大值即为第一问答案。建图如下:
1. 拆点,将序列中每个元素点拆成入点和出点,对于每对入点和出点,从入点向出点连流量 1 的边;
2. 建总源点 S,从 S 向每个元素点的入点连流量 1 的边;
3. 建总汇点 T,从每个元素点的出点向 T 连流量 1 的边;
4. 对于每对元素 x[i] 和 x[j],不妨设 i > j,当且仅当 x[i] > x[j] 且 f[i] = f[j] + 1 时,从元素 x[j] 点的出点向元素 x[i] 点的入点连流量 1 的边。
该图最大流的值即为第二问答案。在该图的基础上建图如下:
1. 将元素 x[1] 点的从入点向出点的边的流量设为 INF;
2. 将元素 x[n] 点的从入点向出点的边的流量设为 INF。
该图最大流的值即为第三问答案。原题数据有误,所求为最长非降子序列。代码如下:

#include
MaxFlow net;
int f[1005],x[1005];
int main()
{
    int m,n,s=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x[i]);
        f[i]=1;
        for(int j=1;jif(x[i]>=x[j]&&f[i]1)
                f[i]=f[j]+1;
        if(f[i]>s)
            s=f[i];
    }
    printf("%d\n",s);
    net.s=n<<1^1,net.t=net.v=net.s+1;
    for(int i=1;i<=n;i++)
    {
        net.AddEdge(i,i+n,1);
        if(f[i]==1)
            net.AddEdge(net.s,i,1);
        if(f[i]==s)
            net.AddEdge(i+n,net.t,1);
        for(int j=1;jif(x[i]>=x[j]&&f[i]==f[j]+1)
                net.AddEdge(j+n,i,1);
    }
    printf("%d\n",m=net.Solve());
    net.AddEdge(net.s,1,INF);
    if(f[n]==s)
        net.AddEdge(n<<1,net.t,INF);
    net.AddEdge(1,1+n,INF);
    net.AddEdge(n,n<<1,INF);
    printf("%d",m+=net.Solve());
    return 0;
}

第七题 试题库问题

最大流。将类型和试题看作点。建图如下:
1. 建总源点 S,从 S 向每个类型点连流量为该类型所需试题数的边;
2. 建总汇点 T,从每个试题点向 T 连流量 1 的边;
3. 对于每个试题点所属于的每个类型点,从该类型点向该试题点连流量 1 的边。
该图最大流是否等于所有类型所需试题总数即为答案。代码如下:

#include
MaxFlow net;
int main()
{
    int c,k,n,p,s=0;
    scanf("%d %d",&k,&n);
    net.s=k+n+1,net.t=net.v=net.s+1;
    for(int i=1;i<=k;i++)
        scanf("%d",&c),s+=c,
        net.AddEdge(net.s,i,c);
    for(int i=k+1;i<=k+n;i++)
    {
        net.AddEdge(i,net.t,1);
        scanf("%d",&p);
        while(p--)
            scanf("%d",&c),
            net.AddEdge(c,i,1);
    }
    printf("%s",net.Solve()==s?"1":"No Solution!");
    return 0;
}

第八题 机器人路径规划问题

费用流。笔者才疏学浅,难以解决该问题。

第九题 方格取数问题

最小割。建图如下:
1. 将原图黑白染色,使同色格不相邻;
2. 建总源点 S,从 S 向每个黑格点连流量为该格数值的边;
3. 对于每对相邻的黑格点和白格点,从黑格点向白格点连流量 INF 的边;
4. 建总汇点 T,从每个白格点向 T 连流量为该格数值的边。
原图所有格总数值减去该图最大流的值即为答案。代码如下:

#include
MaxFlow net;
int main()
{
    int c,m,n,s=0;
    scanf("%d %d",&m,&n);
    net.s=m*n+1,net.t=net.v=net.s+1;
    for(int i=0;i<m;i++)
        for(int j=0;j"%d",&c);
            s+=c;
            if((i+j&1)==0)
            {
                net.AddEdge(net.s,i*n+j+1,c);
                if(i>0)
                    net.AddEdge(i*n+j+1,(i-1)*n+j+1,INF);
                if(j>0)
                    net.AddEdge(i*n+j+1,i*n+j,INF);
                if(i+1<m)
                    net.AddEdge(i*n+j+1,(i+1)*n+j+1,INF);
                if(j+1*n+j+1,i*n+j+2,INF);
            }
            else
                net.AddEdge(i*n+j+1,net.t,c);
        }
    printf("%d",s-net.Solve());
    return 0;
}

第十题 餐巾计划问题

费用流。把每天看作点。建图如下:
1. 拆点,将每天点拆成入点和出点;
2. 建总源点 S,从 S 向每天点的入点连流量为该天所需餐巾数,费用 0 的边;
3. 建总汇点 T,从每天点的出点向 T 连流量为该天所需餐巾数,费用 0 的边;
4. 从 S 向每天点的出点连流量 INF,费用为一块新餐巾费用的边;
5. 从每天点的入点向一天后点的入点连流量 INF,费用 0 的边;
6. 从每天点的入点向快洗所需天数后的出点连流量 INF,费用为一块餐巾快洗所需费用的边;
7. 从每天点的入点向慢洗所需天数后的出点连流量 INF,费用为一块餐巾慢洗所需费用的边。
该图最小费用流的值即为答案。代码如下:

#include
MinCost net;
int main()
{
    int f,m,N,n,p,r,s;
    scanf("%d %d %d %d %d %d",&N,&p,&m,&f,&n,&s);
    net.s=N<<1^1,net.t=net.v=net.s+1;
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&r);
        net.AddEdge(net.s,i,r,0);
        net.AddEdge(i+N,net.t,r,0);
        net.AddEdge(net.s,i+N,INF,p);
        if(i1,INF,0);
        if(i+m<=N)
            net.AddEdge(i,i+m+N,INF,f);
        if(i+n<=N)
            net.AddEdge(i,i+n+N,INF,s);
    }
    N=r=0;
    net.Solve(N,r);
    printf("%d",r);
    return 0;
}

第十一题 航空路线问题

费用流。原题即求两条从最西城市到最东城市的不相交路径,使得所经城市最多,把城市看作点。建图如下:
1. 拆点,将每个城市点拆成入点和出点,对于每对入点和出点,从入点向出点连流量 1,费用 1 的边;
2. 从最西城市点的入点向出点连流量 1,费用 0 的边;
3. 从最东城市点的入点向出点连流量 1,费用 0 的边;
4. 对于每条航线相连的两个城市,从西城市点的出点向东城市点的入点连流量 INF,费用 0 的边。
记源点为最西城市点的入点,汇点为最东城市点的出点。该图最大费用流的值即为答案。代码如下:

#include
MinCost net;
char city[1005][20],city1[20],city2[20];
int main()
{
    int c1,c2,n,v;
    scanf("%d %d\n",&n,&v);
    net.s=1,net.t=net.v=n<<1;
    net.AddEdge(1,n+1,1,0);
    net.AddEdge(n,n<<1,1,0);
    for(int i=1;i<=n;i++)
        gets(city[i]),
        net.AddEdge(i,i+n,1,-1);
    while(v--)
    {
        scanf("%s %s",city1,city2);
        c1=c2=0;
        for(int i=1;i<=n&&c1==0;i++)
            if(strcmp(city[i],city1)==0)
                c1=i;
        for(int i=1;i<=n&&c2==0;i++)
            if(strcmp(city[i],city2)==0)
                c2=i;
        if(c10);
        else
            net.AddEdge(c2+n,c1,INF,0);
    }
    n=v=0;
    net.Solve(n,v);
    if(n==2)
        printf("%d",-v);
    else
        printf("No Solution!");
    return 0;
}

第十二题 软件补丁问题

最短路。将当前错误集合记为状态,用二进制表示看作点,补丁表示状态之间的转移,看作边,边权为该补丁耗时,隐式建图。从包含所有错误状态到无错误状态的最短路即为答案。代码如下:

#include
bool inq[1048580];
char str[25];
int dist[1048580],que[1048580];
struct EDGE
{
    int b1,b2,cost,f1,f2;
}patch[105];
int main()
{
    int m,n,quetop=0;
    scanf("%d %d",&n,&m);
    for(int i=0;i<m;i++)
    {
        scanf("%d %s",&patch[i].cost,str);
        patch[i].b1=patch[i].b2=patch[i].f1=patch[i].f2=0;
        for(int j=0;str[j];j++)
            if(str[j]=='+')
                patch[i].b1|=1<else if(str[j]=='-')
                patch[i].b2|=1<"%s",str);
        for(int j=0;str[j];j++)
            if(str[j]=='-')
                patch[i].f1|=1<else if(str[j]=='+')
                patch[i].f2|=1<for(int i=0;i<1<q[i]=false;
    dist[(1<1]=0,inq[(1<=true,que[quetop++]=(1<1;
    for(int i=0;iq[que[i]]=false;
        for(int j=0;j<m;j++)
            if((que[i]&patch[j].b1)==patch[j].b1&&(que[i]&patch[j].b2)==0
                &&dist[que[i]]+patch[j].costif(!inq[que[i]&~patch[j].f1|patch[j].f2])
                    inq[que[i]&~patch[j].f1|patch[j].f2]=true,
                    que[quetop++]=que[i]&~patch[j].f1|patch[j].f2;
            }
    }
    printf("%d",dist[0]0]:0);
    return 0;
}

第十三题 星际转移问题

最大流。从小到大枚举单位时间,将原题转化为判定性问题。把每单位时间的每个太空站看作点,太空船路线看作边。建图如下:
1. 对于每单位时间的每个太空站,从该点向下个单位时间该太空站点连流量 INF 的边;
2. 对于每艘太空船,从该单位时间所位于的太空站点,向下一个单位时间所位于的太空站点,连流量为该太空船可容纳人数的边。
记源点为地球,汇点为月球。使得该图最大流不小于需运送人数的最小单位时间即为答案。代码如下:

#include
MaxFlow net;
struct SHIP
{
    int hpi,r,si[25];
}pi[15];
int main()
{
    int k,m,n,t;
    scanf("%d %d %d",&n,&m,&k);
    for(int i=0;i<m;i++)
    {
        scanf("%d %d",&pi[i].hpi,&pi[i].r);
        for(int j=0;j"%d",&pi[i].si[j]);
        pi[i].si[pi[i].r]=pi[i].si[0];
    }
    net.s=2,net.t=1,net.v=n+2;
    for(t=0;t<=50&&k>0;net.v+=n,k-=net.Solve(),t++)
    {
        for(int i=3;i<=n+2;i++)
            net.AddEdge(i+n*t,i+n*(t+1),INF);
        for(int i=0;i<m;i++)
            net.AddEdge(pi[i].si[t%pi[i].r]+(pi[i].si[t%pi[i].r]>0)*n*t+2,
                pi[i].si[t%pi[i].r+1]+(pi[i].si[t%pi[i].r+1]>0)*n*(t+1)+2,pi[i].hpi);
    }
    printf("%d",k<=0?t:0);
    return 0;
}

第十四题 孤岛营救问题

最短路。分层图,将当前所持钥匙也看作一维状态,求最短路即可。代码如下:

#include
bool inq[12][12][1025];
int dist[12][12][1025];
struct MAP_UNIT
{
    int east,key,north,south,west;
}map[12][12];
struct QUEUE_UNIT
{
    int key,x,y;
}que[102405];
#define BuildDoor(x,y,dir,key) \
{ \
    if(map[x][y].dir>-1) \
        if(key>0) \
            map[x][y].dir|=1<1; \
        else \
            map[x][y].dir=-1; \
}
#define Update(dx,dy,newkey,d) \
    if(dist[que[i].x][que[i].y][que[i].key]+dx+dx][que[i].y+dy][newkey]) \
    { \
        dist[que[i].x+dx][que[i].y+dy][newkey]=dist[que[i].x][que[i].y][que[i].key]+d; \
        if(!inq[que[i].x+dx][que[i].y+dy][newkey]) \
            inq[que[i].x+dx][que[i].y+dy][newkey]=true, \
            que[quetop].x=que[i].x+dx,que[quetop].y=que[i].y+dy,que[quetop++].key=newkey; \
    }
#define Move(dx,dy,dir) \
    if(map[que[i].x][que[i].y].dir>=0 \
        &&(map[que[i].x][que[i].y].dir&que[i].key)==map[que[i].x][que[i].y].dir) \
        Update(dx,dy,que[i].key,1);
int main()
{
    int g,k,m,n,p,quetop=1,x1,x2,y1,y2;
    scanf("%d %d %d%d",&n,&m,&p,&k);
    for(int ix=0;ixfor(int iy=0;iy<m;iy++)
        {
            map[ix][iy].east=iy+1<m?0:-1;
            map[ix][iy].south=ix+10:-1;
            map[ix][iy].west=iy>0?0:-1;
            map[ix][iy].north=ix>0?0:-1;
            map[ix][iy].key=0;
            for(int ik=0;ik<1<q[ix][iy][ik]=false;
        }
    while(k--)
    {
        scanf("%d %d %d %d %d",&x1,&y1,&x2,&y2,&g);
        --x1,--x2,--y1,--y2;
        if(y1+1==y2)
        {
            BuildDoor(x1,y1,east,g);
            BuildDoor(x2,y2,west,g);
        }
        else if(x1+1==x2)
        {
            BuildDoor(x1,y1,south,g);
            BuildDoor(x2,y2,north,g);
        }
        else if(y1-1==y2)
        {
            BuildDoor(x1,y1,west,g);
            BuildDoor(x2,y2,east,g);
        }
        else
        {
            BuildDoor(x1,y1,north,g);
            BuildDoor(x2,y2,south,g);
        }
    }
    scanf("%d",&k);
    while(k--)
        scanf("%d %d %d",&x1,&y1,&g),
        map[x1-1][y1-1].key|=1<1;
    dist[0][0][0]=0,inq[0][0][0]=true,p=INF,que[0].key=que[0].x=que[0].y=0;
    for(int i=0;iq[que[i].x][que[i].y][que[i].key]=false;
        if(que[i].x==n-1&&que[i].y==m-1&&p>dist[n-1][m-1][que[i].key])
            p=dist[n-1][m-1][que[i].key];
        Move(0,1,east);
        Move(1,0,south);
        Move(0,-1,west);
        Move(-1,0,north);
        Update(0,0,que[i].key|map[que[i].x][que[i].y].key,0);
    }
    printf("%d",p1);
    return 0;
}

第十五题 汽车加油行驶问题

最短路。分层图,将当前油量也看作一维状态,求最短路即可。代码如下:

#include
bool inq[105][105][12];
int dist[105][105][12],map[105][105];
struct QUEUE_UNIT
{
    int gas,x,y;
}que[1000005];
#define Update(dx,dy,newgas,d) \
    if(dist[que[i].x][que[i].y][que[i].gas]+dx+dx][que[i].y+dy][newgas]) \
    { \
        dist[que[i].x+dx][que[i].y+dy][newgas]=dist[que[i].x][que[i].y][que[i].gas]+d; \
        if(!inq[que[i].x+dx][que[i].y+dy][newgas]) \
            inq[que[i].x+dx][que[i].y+dy][newgas]=true, \
            que[quetop].x=que[i].x+dx,que[quetop].y=que[i].y+dy,que[quetop++].gas=newgas; \
    }
int main()
{
    int a,b,c,k,n,quetop=1;
    scanf("%d %d %d %d %d",&n,&k,&a,&b,&c);
    for(int ix=1;ix<=n;ix++)
        for(int iy=1;iy<=n;iy++)
        {
            scanf("%d",&map[ix][iy]);
            for(int ik=0;ik<=k;ik++)
                dist[ix][iy][ik]=INF,inq[ix][iy][ik]=false;
        }
    dist[1][1][k]=0,inq[1][1][k]=true,que[0].gas=k,que[0].x=que[0].y=1;
    for(int i=0;iq[que[i].x][que[i].y][que[i].gas]=false;
        if(k>que[i].gas&&map[que[i].x][que[i].y]==1)
        {
            Update(0,0,k,a);
            continue;
        }
        if(k>que[i].gas)
            Update(0,0,k,a+c);
        if(que[i].gas>0)
        {
            if(que[i].y0,1,que[i].gas-1,0);
            if(que[i].x1,0,que[i].gas-1,0);
            if(que[i].y>1)
                Update(0,-1,que[i].gas-1,b);
            if(que[i].x>1)
                Update(-1,0,que[i].gas-1,b);
        }
    }
    a=dist[n][n][k];
    while(k--)
        if(a>dist[n][n][k])
            a=dist[n][n][k];
    printf("%d",a);
    return 0;
}

第十六题 数字梯形问题

费用流。把每个数字看作点。建图如下:
1. 拆点,将每个数字点拆成入点和出点,对于每对入点和出点,从入点向出点连流量 1,费用为该点数字的边;
2. 建总源点 S,从 S 向最上行数字点的入点连流量 1,费用 0 的边;
3. 建总汇点 T,从最下行数字点的出点向 T 连流量 1,费用 0 的边;
4. 对于每个非最下行数字点,从其出点向其左下点入点连流量 1,费用 0 的边;从其出点向其右下点入点连流量 1,费用 0 的边。
该图最大费用流的值即为第一问答案。在该图的基础上,将步骤 1 和步骤 3 所建边的流量设为 INF,该图最大费用流的值即为第二问答案。在该图的基础上,再将步骤 4 所建边的流量设为 INF,该图的最大费用流的值即为第三问答案。代码如下:

#include
MinCost net;
int main()
{
    int ans,c,m,n;
    scanf("%d %d",&m,&n);
    net.s=net.v=1;
    for(int i=1;i<=m;i++)
        net.AddEdge(1,i<<1,1,0);
    for(int i=m;i<m+n;i++)
        for(int j=0;j"%d",&c),
            net.v+=2,
            net.AddEdge(net.v-1,net.v,1,-c);
    net.t=++net.v;
    for(int i=1;i<m+n;i++)
        net.AddEdge(net.t-(i<<1)+1,net.t,1,0);
    c=net.e;
    for(int i=m,k=3;i+1<m+n;i++)
        for(int j=0;j2)
            net.AddEdge(k,(i<<1)+k-1,1,0),
            net.AddEdge(k,(i<<1)+k+1,1,0);
    ans=n=0;
    net.ClearEdgeFlow();
    net.Solve(n,ans);
    printf("%d\n",-ans);
    for(int i=m;i1].cap=INF;
    ans=n=0;
    net.ClearEdgeFlow();
    net.Solve(n,ans);
    printf("%d\n",-ans);
    for(int i=c;i1].cap=INF;
    ans=n=0;
    net.ClearEdgeFlow();
    net.Solve(n,ans);
    printf("%d",-ans);
    return 0;
}

第十七题 运输问题

费用流。把仓库和商店看作点。建图如下:
1. 建总源点 S,从 S 向每个仓库连流量为该仓库货物数,费用 0 的边;
2. 建总汇点 T,从每个商店向 T 连流量为该商店货物数,费用 0 的边;
3. 对于每个仓库与每个商店,从该仓库向该商店连流量 INF,费用为相应费用的边。
该图最小费用流的值即为答案。代码如下:

#include
MinCost net;
int main()
{
    int c,m,n;
    scanf("%d %d",&m,&n);
    net.s=m+n+1,net.t=net.v=net.s+1;
    for(int i=1;i<=m;i++)
        scanf("%d",&c),
        net.AddEdge(net.s,i,c,0);
    for(int i=1;i<=n;i++)
        scanf("%d",&c),
        net.AddEdge(i+m,net.t,c,0);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&c),
            net.AddEdge(i,j+m,INF,c);
    c=m=0;
    net.Solve(m,c);
    printf("%d\n",c);
    net.ClearEdgeFlow();
    net.NegateEdgeCost();
    c=m=0;
    net.Solve(m,c);
    printf("%d",-c);
    return 0;
}

第十八题 分配问题

费用流。把人和工作看作点。建图如下:
1. 建总源点 S,从 S 向每个人连流量 1,费用 0 的边;
2. 建总汇点 T,从每件工作向 T 连流量 1,费用 0 的边;
3. 对于每个人与每件工作,从该人向该工作连流量 INF,费用为相应效益的边。
该图最大费用流的值即为答案。代码如下:

#include
MinCost net;
int main()
{
    int c,n;
    scanf("%d",&n);
    net.s=n<<1^1,net.t=net.v=net.s+1;
    for(int i=1;i<=n;i++)
        net.AddEdge(net.s,i,1,0),
        net.AddEdge(i+n,net.t,1,0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&c),
            net.AddEdge(i,j+n,INF,c);
    c=n=0;
    net.Solve(n,c);
    printf("%d\n",c);
    net.ClearEdgeFlow();
    net.NegateEdgeCost();
    c=n=0;
    net.Solve(n,c);
    printf("%d",-c);
    return 0;
}

第十九题 负载平衡问题

费用流。把仓库看作点。建图如下:
1. 拆点,将每个仓库点拆成入点和出点,对于每对入点和出点,从入点向出点连流量 INF,费用 0 的边;从出点向入点连流量 INF,费用 0 的边;
2. 建总源点 S,从 S 向每个仓库点的入点连流量为该仓库货物数,费用 0 的边;
3. 对于每个仓库,从该仓库点的出点向上个仓库点的入点连流量 INF,费用 1 的边;从该仓库点的出点向下个仓库点的入点连流量 INF,费用 1 的边;
4. 建总汇点 T,从每个仓库点的出点向 T 连流量为每个仓库货物数的平均数,费用 0 的边。
该图最小费用流的值即为答案。代码如下:

#include
MinCost net;
int main()
{
    int c,m=0,n;
    scanf("%d",&n);
    net.s=n<<1^1,net.t=net.v=net.s+1;
    for(int i=1;i<=n;i++,m+=c)
        scanf("%d",&c),
        net.AddEdge(net.s,i,c,0);
    m/=n;
    for(int i=1;i<=n;i++)
        net.AddEdge(i,i+n,INF,0),
        net.AddEdge(i+n,i,INF,0),
        net.AddEdge(i,(i+n-2)%n+n+1,INF,1),
        net.AddEdge(i,i%n+n+1,INF,1),
        net.AddEdge(i+n,net.t,m,0);
    c=m=0;
    net.Solve(m,c);
    printf("%d",c);
    return 0;
}

第二十题 深海机器人问题

费用流。把网格点看作点,网格线看作边。建图如下:
1. 从每个网格点向其东网格点连流量 1,费用为相应价值的边;从每个网格点向其北网格点连流量 1,费用 0 的边;
2. 从每个网格点向其东网格点连流量 INF,费用 0 的边;从每个网格点向其北网格点连流量 INF,费用 0 的边;
3. 建总源点 S,从 S 向每个出发点连流量为该出发点机器人数,费用 0 的边;
4. 建总汇点 T,从每个目的点向 T 连流量为该目的点机器人数,费用 0 的边。
该图最大费用流的值即为答案。代码如下:

#include
MinCost net;
int main()
{
    int a,b,c,p,q,x,y;
    scanf("%d %d%d %d",&a,&b,&p,&q);
    net.s=(p+1)*(q+1)+1,net.t=net.v=net.s+1;
    for(int i=0;i<=p;i++)
        for(int j=0;j<q;j++)
            scanf("%d",&c),
            net.AddEdge(i*(q+1)+j+1,i*(q+1)+j+2,1,-c),
            net.AddEdge(i*(q+1)+j+1,i*(q+1)+j+2,INF,0);
    for(int i=0;i<=q;i++)
        for(int j=0;j"%d",&c),
            net.AddEdge(i+j*(q+1)+1,i+(j+1)*(q+1)+1,1,-c),
            net.AddEdge(i+j*(q+1)+1,i+(j+1)*(q+1)+1,INF,0);
    while(a--)
        scanf("%d %d %d",&c,&x,&y),
        net.AddEdge(net.s,(q+1)*x+y+1,c,0);
    while(b--)
        scanf("%d %d %d",&c,&x,&y),
        net.AddEdge((q+1)*x+y+1,net.t,c,0);
    c=p=0;
    net.Solve(p,c);
    printf("%d",-c);
    return 0;
}

第廿一题 最长 k 可重区间集问题

费用流。建图如下:
1. 将端点值离散化,以端点值建点;
2. 建总源点 S,从 S 向最小端点连流量为可重数,费用 0 的边;
3. 对于每个区间,从区间左端点向区间右端点连流量 1,费用为区间长度的边;
4. 建总汇点 T,从最大端点向 T 连流量为可重数,费用 0 的边。
该图最大费用流的值即为答案。代码如下:

#include
#include
MinCost net;
int interv[1005],interval[1005],power[1005];
struct ENDPOINT
{
    int interv,num;
}endpoint[2005];
int cmp(const void *p,const void *q)
{
    return (*(ENDPOINT *)p).num>(*(ENDPOINT *)q).num?1:-1;
}
int main()
{
    int k,n;
    scanf("%d %d",&n,&k);
    for(int i=0;i
        endpoint[i<<1].interv=endpoint[i<<1^1].interv=i+1,
        scanf("%d %d",&endpoint[i<<1].num,&endpoint[i<<1^1].num),
        power[i+1]=endpoint[i<<1].num1^1].num?1:-1;
    qsort(endpoint,n<<1,sizeof(ENDPOINT),cmp);
    memset(interv,0,sizeof(interv));
    net.s=1,net.v=2;
    net.AddEdge(net.s,net.v,k,0);
    interv[endpoint[0].interv]=2;
    interval[endpoint[0].interv]=endpoint[0].num;
    for(int i=1;i
    {
        if(endpoint[i-1].num!=endpoint[i].num)
            ++net.v,net.AddEdge(net.v-1,net.v,INF,0);
        if(interv[endpoint[i].interv]>0)
            net.AddEdge(interv[endpoint[i].interv],net.v,1,(interval[endpoint[i].interv]-endpoint[i].num)*power[endpoint[i].interv]);
        else
            interv[endpoint[i].interv]=net.v,
            interval[endpoint[i].interv]=endpoint[i].num;
    }
    net.t=++net.v;
    net.AddEdge(net.v-1,net.t,k,0);
    k=n=0;
    net.Solve(k,n);
    printf("%d",-n);
    return 0;
}

第廿二题 最长 k 可重线段集问题

费用流。同第廿一题,亦可建图如下:
1. 将线段按左端点升序排序;
2. 建总源点 S,建限流点 S’,从 S 向 S’ 连流量为可重数,费用 0 的边;
3. 拆点,对于每条线段,设左端点为入点,右端点为出点,对于每对入点和出点,从入点向出点连流量 1,费用为线段长度的边;
4. 从 S’ 向每条线段的入点连流量 1,费用 0 的边;
5. 建总汇点 T,从每条线段的出点向 T 连流量 1,费用 0 的边;
6. 对于每对线段 i 和线段 j,不妨设 i > j,若有线段 i 的左端点的横坐标大于线段 j 的右端点的横坐标,则从线段 j 的出点向线段 i 的入点连流量 1,费用 0 的边。
该图最大费用流的值即为答案。原题数据有误,笔者代码正确性有待验证。代码如下:

#include
#include
#include
#define QueryDistance(x0,y0,x1,y1) ((int)sqrt(((x1)-(x0))*(long long)((x1)-(x0))+((y1)-(y0))*(long long)((y1)-(y0))))
MinCost net;
struct INTERVAL
{
    int x0,x1,y0,y1;
}interval[1005];
int cmp(const void *p,const void *q)
{
    if((*(INTERVAL *)p).x0==(*(INTERVAL *)q).x0)
        return (*(INTERVAL *)p).x1>(*(INTERVAL *)q).x1?1:-1;
    return (*(INTERVAL *)p).x0>(*(INTERVAL *)q).x0?1:-1;
}
int main()
{
    int k,n;
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%d %d %d %d\n",&interval[i].x0,&interval[i].y0,&interval[i].x1,&interval[i].y1);
    qsort(interval+1,n,sizeof(INTERVAL),cmp);
    net.s=n+1<<1,net.t=net.v=net.s+1;
    net.AddEdge(net.s,net.s-1,k,0);
    for(int i=1;i<=n;i++)
    {
        net.AddEdge(net.s-1,i,1,0);
        net.AddEdge(i+n,net.t,1,0);
        net.AddEdge(i,i+n,1,-QueryDistance(interval[i].x0,interval[i].y0,interval[i].x1,interval[i].y1));
        for(int j=1;j
            if(interval[i].x0>interval[j].x1||interval[i].x0!=interval[i].x1&&interval[j].x0!=interval[j].x1&&interval[i].x0>=interval[j].x1)
                net.AddEdge(j+n,i,1,0);
    }
    k=n=0;
    net.Solve(k,n);
    printf("%d",-n);
    return 0;
}

第廿三题 火星探险问题

费用流。把网格点看作点,网格线看作边。建图如下:
1. 拆点,将每个网格点拆成入点和出点;
2. 对于每个网格点,若该网格点为石块,则从该网格点的入点向该网格点的出点连流量 1,费用 1 的边;
3. 对于每个网格点,若该网格点非障碍,则从该网格点的入点向该网格点的出点连流量 INF,费用 0 的边;
4. 从每个网格点的出点向其东网格点的入点连流量 INF,费用 0 的边;从每个网格点的出点向其南网格点的入点连流量 INF,费用 0 的边。
该图最大费用流的值即为答案。代码如下:

#include
MinCost net;
int main()
{
    int c,n,p,q;
    scanf("%d%d%d",&n,&p,&q);
    net.s=p*q<<1^1,net.t=net.v=net.s+1;
    net.AddEdge(net.s,1,n,0);
    net.AddEdge(p*q<<1,net.t,n,0);
    for(int i=0;i<q;i++)
        for(int j=0;j"%d",&c);
            if(c==2)
                net.AddEdge(i+j*q+1,i+j*q+p*q+1,1,-1);
            if(c!=1)
                net.AddEdge(i+j*q+1,i+j*q+p*q+1,INF,0);
            if(i+1<q)
                net.AddEdge(i+j*q+p*q+1,i+j*q+2,INF,0);
            if(j+1*q+p*q+1,i+(j+1)*q+1,INF,0);
        }
    c=n=0;
    net.Solve(n,c);
    printf("%d %d",n,-c);
    return 0;
}

第廿四题 骑士共存问题

最小割。建图如下:
1. 将原图黑白染色,使同色格不相邻;
2. 建总源点 S,从 S 向每个黑格点连流量 1 的边;
3. 对于每个黑格点,从该点向马步可到达的非障碍点连流量 INF 的边;
4. 建总汇点 T,从每个白格点向 T 连流量 1 的边。
原图所有非障碍格总数减去该图最大流的值即为答案。代码如下:

#include
MaxFlow net;
bool map[205][205];
#define Link(x,y) \
    if((x)>=0&&(y)>=0&&(x)y)map[x][y]) \
        net.AddEdge(i*n+j+1,(x)*n+(y)+1,INF);
int main()
{
    int m,n,x,y;
    scanf("%d %d",&n,&m);
    net.s=n*n+1,net.t=net.v=net.s+1;
    memset(map,true,sizeof(map));
    for(int i=0;i<m;i++)
        scanf("%d %d",&x,&y),
        map[x-1][y-1]=false;
    for(int i=0;ifor(int j=0;jif(map[i][j])
                if((i+j&1)==0)
                {
                    net.AddEdge(net.s,i*n+j+1,1);
                    Link(i-2,j+1);
                    Link(i-1,j+2);
                    Link(i+1,j+2);
                    Link(i+2,j+1);
                    Link(i+2,j-1);
                    Link(i+1,j-2);
                    Link(i-1,j-2);
                    Link(i-2,j-1);
                }
                else
                    net.AddEdge(i*n+j+1,net.t,1);
    printf("%d",n*n-m-net.Solve());
    return 0;
}

你可能感兴趣的:(题解)