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

网络流模板

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

#include<string.h>
#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;i<QueueTop;i++)
            for(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;i<e<<1;i++)
            edge[i].flow=0;
    }
    void NegateEdgeCost()
    {
        for(int i=0;i<e<<1;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;i<QueueTop;i++)
        {
            inq[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].cost<dist[edge[j].to])
                {
                    dist[edge[j].to]=dist[que[i]]+edge[j].cost;
                    flow[edge[j].to]=Min_Integer(flow[que[i]],edge[j].cap-edge[j].flow);
                    prev[edge[j].to]=j;
                    if(!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<stdio.h>
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<stdio.h>
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<stdio.h>
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<stdio.h>
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<stdio.h>
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<stdio.h>
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;j<i;j++)
            if(x[i]>=x[j]&&f[i]<f[j]+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;j<i;j++)
            if(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<stdio.h>
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<stdio.h>
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<n;j++)
        {
            scanf("%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)
                    net.AddEdge(i*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<stdio.h>
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(i<N)
            net.AddEdge(i,i+1,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<stdio.h>
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(c1<c2)
            net.AddEdge(c1+n,c2,INF,0);
        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<stdio.h>
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<<j;
            else if(str[j]=='-')
                patch[i].b2|=1<<j;
        scanf("%s",str);
        for(int j=0;str[j];j++)
            if(str[j]=='-')
                patch[i].f1|=1<<j;
            else if(str[j]=='+')
                patch[i].f2|=1<<j;
    }
    for(int i=0;i<1<<n;i++)
        dist[i]=INF,inq[i]=false;
    dist[(1<<n)-1]=0,inq[(1<<n)-1]=true,que[quetop++]=(1<<n)-1;
    for(int i=0;i<quetop;i++)
    {
        inq[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].cost<dist[que[i]&~patch[j].f1|patch[j].f2])
            {
                dist[que[i]&~patch[j].f1|patch[j].f2]=dist[que[i]]+patch[j].cost;
                if(!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]<INF?dist[0]:0);
    return 0;
}

第十三题 星际转移问题

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

#include<stdio.h>
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<pi[i].r;j++)
            scanf("%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<stdio.h>
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<<key-1; \
        else \
            map[x][y].dir=-1; \
}
#define Update(dx,dy,newkey,d) \
    if(dist[que[i].x][que[i].y][que[i].key]+d<dist[que[i].x+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;ix<n;ix++)
        for(int iy=0;iy<m;iy++)
        {
            map[ix][iy].east=iy+1<m?0:-1;
            map[ix][iy].south=ix+1<n?0:-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<<p;ik++)
                dist[ix][iy][ik]=INF,inq[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<<g-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;i<quetop;i++)
    {
        inq[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",p<INF?p:-1);
    return 0;
}

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

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

#include<stdio.h>
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]+d<dist[que[i].x+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;i<quetop;i++)
    {
        inq[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].y<n)
                Update(0,1,que[i].gas-1,0);
            if(que[i].x<n)
                Update(1,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<stdio.h>
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<i;j++)
            scanf("%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;j<i;j++,k+=2)
            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;i<c;i++)
        net.edge[i<<1].cap=INF;
    ans=n=0;
    net.ClearEdgeFlow();
    net.Solve(n,ans);
    printf("%d\n",-ans);
    for(int i=c;i<net.e;i++)
        net.edge[i<<1].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<stdio.h>
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<stdio.h>
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<stdio.h>
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<stdio.h>
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<p;j++)
            scanf("%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<stdio.h>
#include<stdlib.h>
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<n;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].num<endpoint[i<<1^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<n<<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<math.h>
#include<stdio.h>
#include<stdlib.h>
#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<i;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<stdio.h>
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<p;j++)
        {
            scanf("%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<p)
                net.AddEdge(i+j*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<stdio.h>
MaxFlow net;
bool map[205][205];
#define Link(x,y) \
    if((x)>=0&&(y)>=0&&(x)<n&&(y)<n&&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;i<n;i++)
        for(int j=0;j<n;j++)
            if(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;
}
编号 问名称 问模型 转化模型 1 飞行员配对方案问 二分图最大匹配 网络最大 2 太空飞行计划问 最大权闭合图 网络最小割 3 最小路径覆盖问 有向无环图最小路径覆盖 网络最大 4 魔术球问 有向无环图最小路径覆盖 网络最大 5 圆桌问 二分图多重匹配 网络最大 6 最长递增子序列问 最多不相交路径 网络最大 7 试库问 二分图多重匹配 网络最大 8 机器人路径规划问 (未解决) 最小费用最大 9 方格取数问 二分图点权最大独立集 网络最小割 10 餐巾计划问 线性规划网络优化 最小费用最大 11 航空路线问 最长不相交路径 最小费用最大 12 软件补丁问 最小转移代价 最短路径 13 星际转移问 网络判定 网络最大 14 孤岛营救问 分层图最短路径 最短路径 15 汽车加油行驶问 分层图最短路径 最短路径 16 数字梯形问 最大权不相交路径 最小费用最大 17 运输问 网络费用量 最小费用最大 18 分配问 二分图最佳匹配 最小费用最大 19 负载平衡问 最小代价供求 最小费用最大 20 深海机器人问 线性规划网络优化 最小费用最大 21 最长k可重区间集问 最大权不相交路径 最小费用最大 22 最长k可重线段集问 最大权不相交路径 最小费用最大 23 火星探险问 线性规划网络优化 最小费用最大 24 骑士共存问 二分图最大独立集 网络最小割
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值