网络流模板
笔者最大流使用 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;
}