食用手册

网络流24题除了暂时没有优秀解法的“机器人路径规划问题”以外,皆可在loj上食用。
关于网络流的部分问题,可参见本蒟蒻“网络流”专题的博客。
EK最大流算法
dinic最大流算法
最小费用最大流
以及安利xzy神犇的binic(即“比你快”算法)最大流算法:http://k-xzy.cf/archives/3008
祝食用愉快。

1.飞行员配对方案问题

二分图匹配问题,图方便还是用了匈牙利算法。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int cp[105],vis[105],h[105],to[100005],ne[100005];
int n,m,ans,tot;
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
int dfs(int x) {for(int i=h[x];i;i=ne[i])if(!vis[to[i]]) {vis[to[i]]=1;if(!cp[to[i]]||dfs(cp[to[i]])) {cp[to[i]]=x;return 1;}}return 0;
}
int main()
{int x,y;scanf("%d%d",&n,&m);while(scanf("%d%d",&x,&y)==2) add(x,y);for(int i=1;i<=n;++i) {for(int j=1;j<=m;++j) vis[j]=0;if(dfs(i)) ++ans;}printf("%d",ans);return 0;
}

2.太空飞行计划问题

最大权闭合子图问题。
如果您需要理性的证明,参见胡伯涛的论文或者:这里(然而这里其实也没有什么太严谨的证明,还是看论文吧)
首先源点向所有实验连一条(s,实验编号,实验可得费用)的边,所有仪器向汇点连一条(仪器编号,t,仪器花费)的边。然后每一个实验向所需的仪器连边。
我们继续把网络流比作水流的方法来看这个建图,每个实验上汇集了一些水,但是会随着所需的仪器”流走”,流走后可能还剩了一点水,这就是通过这个实验能赚的钱,综上,答案应该为“实验总可得费用-最大流”。
然后看方案。跑完最大流后的残图中,如果源点还可达某个点,这个点是实验,说明还有“残留的水”,该实验可取。如果是仪器,因为残图一定被“割开”了,源点能到的点到不了汇点,所以说明这个仪器被选用了,因此我们可以得到方案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=(105+50*50)*2;
int n,m,tot=1,s,t,ans;
int h[105],to[N],ne[N],flow[N],q[105],lev[105],vis[105];
void add(int x,int y,int z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {if(x==t) return liu;int sum=0,kl;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&lev[to[i]]==lev[x]+1) {kl=dfs(to[i],min(liu-sum,flow[i]));sum+=kl,flow[i]-=kl,flow[i^1]+=kl;if(sum==liu) return sum;}return sum;
}
int bfs() {int x,he=1,ta=1;for(int i=s;i<=t;++i) lev[i]=0;lev[s]=1,q[1]=s;while(he<=ta) {x=q[he],++he;if(x==t) return 1;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&!lev[to[i]])lev[to[i]]=lev[x]+1,q[++ta]=to[i];}return 0;
}
void gs(int x) {for(int i=h[x];i;i=ne[i])if(!vis[to[i]]&&flow[i]>0) vis[to[i]]=1,gs(to[i]);
}
int main()
{int x;scanf("%d%d",&m,&n);s=0,t=m+n+1;for(int i=1;i<=m;++i) {scanf("%d",&x),add(s,i,x),ans+=x;char ch=getchar();while(ch!='\n'&&ch!='\r'&&ch!=EOF) {x=0;while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();if(x) add(i,m+x,inf);if(ch!='\n'&&ch!='\r'&&ch!=EOF) ch=getchar();}}for(int i=1;i<=n;++i) scanf("%d",&x),add(i+m,t,x);while(bfs()) ans-=dfs(s,inf);vis[s]=1,gs(s);for(int i=1;i<=m;++i) if(vis[i]) printf("%d ",i);putchar('\n');for(int i=1;i<=n;++i) if(vis[i+m]) printf("%d ",i);putchar('\n');printf("%d",ans);return 0;
}

3.最小路径覆盖问题

首先每个点是一条单独的路径,每次可以选择一条边,使得两个路径被合并起来,这是一个二分图匹配问题。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=205,M=6005;
int n,m,tot,ans;
int h[N],ne[M],to[M],pre[N],vis[N],nxt[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
int dfs(int x) {for(int i=h[x];i;i=ne[i])if(!vis[to[i]]) {vis[to[i]]=1;if(!pre[to[i]]||dfs(pre[to[i]])) {pre[to[i]]=x,nxt[x]=to[i];return 1;}}return 0;
}
int main()
{int x,y;scanf("%d%d",&n,&m);for(int i=1;i<=m;++i) scanf("%d%d",&x,&y),add(x,y);ans=n;for(int i=1;i<=n;++i) {for(int j=1;j<=n;++j) vis[j]=0;if(dfs(i)) --ans;}for(int i=1;i<=n;++i)if(!pre[i]) {x=i;while(x) printf("%d ",x),x=nxt[x];putchar('\n');}printf("%d",ans);return 0;
}

4.魔术球问题

其实可以贪心做(汗
参考最小路径覆盖问题,二分球的个数,对每个球建点,x向满足x+y是完全平方数且y>x的一个y连边,跑最小路径覆盖检验答案,如果所需路径小于等于n即满足条件。
如果用匈牙利算法的话,二分还要卡卡常(先打出最大的答案,然后嘿嘿嘿)
当然,其实如果使用网络流来搞二分图匹配的花,每次加一条边后继续跑网络流是很快的,因为原本就已经生成了残量网络了……枚举过毫无压力……

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=500005;
int n,tot,ans,t;
int h[N],ne[N],to[N],flow[N],vis[N],pre[N],nxt[N];
void add(int x,int y){to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
int dfs(int x) {//辣鸡匈牙利算法for(int i=h[x];i;i=ne[i])if(!vis[to[i]]) {vis[to[i]]=1;if(!pre[to[i]]||dfs(pre[to[i]])){pre[to[i]]=x,nxt[x]=to[i];return 1;}}return 0;
}
int work(int lim) {//辣鸡建图+检验答案int x1=lim*2-1,x2=lim*2,js=lim;for(int i=1;i<=lim*2;++i) h[i]=pre[i]=nxt[i]=0;tot=0;for(int i=1;i<=lim;++i) {for(int j=sqrt(i);j*j<i*2;++j)if(j*j>i) add((j*j-i)*2-1,i*2);}for(int i=1;i<=lim*2;i+=2) {for(int j=2;j<=lim*2;j+=2) vis[j]=0;if(dfs(i)) --js;}if(js>n) return 0;return 1;
}
int main()
{scanf("%d",&n);int l=n,r=1567,mid;while(l<=r) {//二分答案mid=(l+r)>>1;if(work(mid)) ans=mid,l=mid+1;else r=mid-1;}printf("%d\n",ans);work(ans);for(int i=1;i<=ans;++i)if(!pre[i*2]) {int x=i*2-1;while(x+1!=0) printf("%d ",x/2+1),x=nxt[x]-1;putchar('\n');}return 0;
}

5.圆桌问题

其实可以贪心做(汗
最小割问题。首先把每一个代表团建一个点,s向它们各连一条流量为代表团人数的边。然后每一个代表团向每一张餐桌连一条流量为1的边,然后每个餐桌向汇点连一条流量为餐桌可容纳人数的边。最后看哪些边被割掉了即可求解。
由于建图比较直观就不作更多说明了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=450,M=100005,inf=0x3f3f3f3f;
int h[N],to[M],ne[M],flow[M],lev[N],q[N];
int tot=1,n,m,s,t,sum;
void add(int x,int y,int z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {if(x==t) return liu;int kl,sum=0;for(int i=h[x];i;i=ne[i])if(lev[to[i]]==lev[x]+1&&flow[i]>0) {kl=dfs(to[i],min(flow[i],liu-sum));sum+=kl,flow[i]-=kl,flow[i^1]+=kl;if(sum==liu) return sum;}return sum;
}
int bfs() {int he=1,ta=1,x;for(int i=s;i<=t;++i) lev[i]=0;lev[s]=1,q[1]=s;while(he<=ta) {x=q[he],++he;if(x==t) return 1;for(int i=h[x];i;i=ne[i])if(!lev[to[i]]&&flow[i]>0)lev[to[i]]=lev[x]+1,q[++ta]=to[i];}return 0;
}
int main()
{int x,js=0;scanf("%d%d",&m,&n);s=0,t=m+n+1;for(int i=1;i<=m;++i) scanf("%d",&x),sum+=x,add(s,i,x);for(int i=1;i<=n;++i) scanf("%d",&x),add(i+m,t,x);for(int i=1;i<=m;++i)for(int j=1;j<=n;++j) add(i,j+m,1);while(bfs()) js+=dfs(s,inf);if(js!=sum) puts("0");else {puts("1");for(int i=1;i<=m;++i) {for(int j=h[i];j;j=ne[j])if(flow[j]==0&&to[j]!=s) printf("%d ",to[j]-m);putchar('\n');}}return 0;
}

6.最长递增子序列问题

这题题面简直有点不知所云。第二问是同时选出长度为s的递增序列(不可重叠),最多可以选多少个。第三问中不可以连续使用多个x1x_1和xnx_n
这题的建图很巧妙,给跪了。
首先用O(n2)O(n^2)的dp瞎搞一遍,获得从每一个位置i开始可以构成的最长子序列长度fif_i
将每一个点拆成i1i_1和i2i_2。令lengthlength表示最长递增子序列长度,那么对于每一个点,如果fi=lengthf_i=length,连边(s,i1,1)(s,i_1,1),因为它可以作为一个序列的起点。如果fi=1f_i=1,连边(i2,t,1)(i_2,t,1),因为它可以作为一个序列的终点。此外,连边(i1,i2,1)(i_1,i_2,1),这是保证每一个点只被用一次。最后,如果存在j>ij>i且fj+1=fif_j+1=f_i且aj≥aia_j \geq a_i,那么连边(i2,j1,1)(i_2,j_1,1),这样可以模拟选择序列的过程。
求一遍最大流得到第二问的解。
如果存在(s,11,1)(s,1_1,1),添加(s,11,inf)(s,1_1,inf),然后添加(11,12,inf)(1_1,1_2,inf),(n1,n2,inf)(n_1,n_2,inf),(n2,t,inf)(n_2,t,inf),借着上一问搞完的残量图继续求最大流即可得到第三问的解,这几次修改就是由于x1x_1和xnx_n的选择次数不受控制后引起的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=505,M=500*500*2+5,inf=0x3f3f3f3f;
int n,s,t,len,tot=1,js;
int a[N],h[N<<1],ne[M],to[M],flow[M],lev[N<<1],q[N<<1],f[N];
void add(int x,int y,int z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {if(x==t) return liu;int sum=0,kl;for(int i=h[x];i;i=ne[i])if(lev[to[i]]==lev[x]+1&&flow[i]>0) {kl=dfs(to[i],min(liu-sum,flow[i]));sum+=kl,flow[i]-=kl,flow[i^1]+=kl;if(sum==liu) return sum;}return sum;
}
int bfs() {for(int i=s;i<=t;++i) lev[i]=0;int he=1,ta=1,x;lev[s]=1,q[1]=s;while(he<=ta) {x=q[he],++he;if(x==t) return 1;for(int i=h[x];i;i=ne[i])if(!lev[to[i]]&&flow[i]>0)lev[to[i]]=lev[x]+1,q[++ta]=to[i];}return 0;
}
int main()
{scanf("%d",&n);s=0,t=n*2+1;for(int i=1;i<=n;++i) scanf("%d",&a[i]);for(int i=n;i>=1;--i) {f[i]=1;for(int j=i+1;j<=n;++j)if(a[j]>=a[i]&&f[j]+1>f[i]) f[i]=f[j]+1;len=max(len,f[i]);}printf("%d\n",len);for(int i=1;i<=n;++i) {if(f[i]==len) add(s,i,1);if(f[i]==1) add(i+n,t,1);add(i,i+n,1);for(int j=i+1;j<=n;++j)if(a[j]>=a[i]&&f[i]==f[j]+1) add(i+n,j,1);}while(bfs()) js+=dfs(s,inf);printf("%d\n",js);if(f[1]==len) add(s,1,inf);add(1,n+1,inf),add(n,n+n,inf),add(n+n,t,inf);while(bfs()) js+=dfs(s,inf);printf("%d\n",js);return 0;
}

7.试题库问题

这……这不是个easy的类二分图匹配问题吗╮(╯_╰)╭
那么只要源点往每个题上连一条流量为1的边,题往其特征上连一条流量为1的边,特征往汇点上连一条流量为该特征需求量的边,然后跑最大流检验答案并输出方案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1025,M=20*1000*2+1025,inf=0x3f3f3f3f;
int s,t,n,m,tot=1,sum,js;
int h[N],ne[M],to[M],flow[M],q[N],lev[N];
void add(int x,int y,int z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {if(x==t) return liu;int sum=0,kl;for(int i=h[x];i;i=ne[i])if(lev[to[i]]==lev[x]+1&&flow[i]>0) {kl=dfs(to[i],min(liu-sum,flow[i]));sum+=kl,flow[i]-=kl,flow[i^1]+=kl;if(sum==liu) return sum;}return sum;
}
int bfs() {for(int i=s;i<=t;++i) lev[i]=0;int he=1,ta=1,x;lev[s]=1,q[1]=s;while(he<=ta) {x=q[he],++he;if(x==t) return 1;for(int i=h[x];i;i=ne[i])if(!lev[to[i]]&&flow[i]>0)lev[to[i]]=lev[x]+1,q[++ta]=to[i];}return 0;
}
int main()
{int x,num;scanf("%d%d",&m,&n);s=0,t=n+m+1;for(int i=1;i<=m;++i) scanf("%d",&x),add(i+n,t,x),sum+=x;for(int i=1;i<=n;++i) {scanf("%d",&num);add(s,i,1);for(int j=1;j<=num;++j) scanf("%d",&x),add(i,x+n,1);}while(bfs()) js+=dfs(s,inf);if(js!=sum) {puts("No Solution!");return 0;}for(int i=1;i<=m;++i) {printf("%d:",i);for(int j=h[i+n];j;j=ne[j])if(flow[j^1]==0&&to[j]!=t) printf(" %d",to[j]);putchar('\n');}return 0;
}

8.机器人路径规划问题

–此问题未解决,溜了溜了,如果本蒟蒻哪天脑子抽了可能会用搜索之类的方法试一下这题–

9.方格取数问题

最大点权独立集问题。
我们可以将原图交错染成天依蓝和nico粉两色,则分成了蓝色组和粉色组。
很不幸,相邻的蓝色点和粉色点是无法共存的。
我们先求解第一个问题:一条边相连的两点至少选一个,得到的最小权值和。
我们首先选择所有的蓝色点,即将源点和所有的蓝色点连一条流量为蓝色点点权的边。而将所有粉色点和汇点连一条流量为粉色点点权的边。
现在我们要改变我们既定的选择。由于当前流量已经确定了,所以我们用图内流量去“填”粉色点代表的边的时候,不可能发生权值增多,只有可能减少或者不变。这样感性地理解一下,会发现最后得到的最大流就是问题一的解。
而我们现在的问题是一条边相连的两点只能选一个,求最大权值和。
则用权值总和-最大流即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=30*30+5,M=30*30*10+5,inf=0x3f3f3f3f;
int mvx[6]={0,1,-1,0,0},mvy[6]={0,0,0,1,-1};
int h[N],ne[M],to[M],flow[M],lev[N],q[N];
int n,m,tot=1,s,t,sss,ans;
void add(int x,int y,int z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {if(x==t) return liu;int kl,sum=0;for(int i=h[x];i;i=ne[i])if(lev[to[i]]==lev[x]+1&&flow[i]>0) {kl=dfs(to[i],min(liu-sum,flow[i]));sum+=kl,flow[i]-=kl,flow[i^1]+=kl;if(sum==liu) return sum;}return sum;
}
int bfs() {for(int i=s;i<=t;++i) lev[i]=0;int x,he=1,ta=1;lev[s]=1,q[1]=s;while(he<=ta) {x=q[he],++he;if(x==t) return 1;for(int i=h[x];i;i=ne[i])if(!lev[to[i]]&&flow[i]>0)q[++ta]=to[i],lev[to[i]]=lev[x]+1;}return 0;
}
int main()
{int x;scanf("%d%d",&n,&m);s=0,t=n*m+1;for(int i=1;i<=n;++i)for(int j=1;j<=m;++j) {scanf("%d",&x),sss+=x;if((i+j)%2==0) {add((i-1)*m+j,t,x);continue;}add(s,(i-1)*m+j,x);for(int k=1;k<=4;++k) {int tx=i+mvx[k],ty=j+mvy[k];if(tx>=1&&tx<=n&&ty>=1&&ty<=m)add((i-1)*m+j,(tx-1)*m+ty,inf);}}while(bfs()) ans+=dfs(s,inf);printf("%d",sss-ans);return 0;
}

10.餐巾计划问题

最小费用最大流QWQ
还是很明显的,首先要把一天拆成两个点,一个表示当天过后脏餐巾i1i_1,一个表示当天开始前的干净餐巾i2i_2。
由于干净餐巾需要rir_i个,所以连边(i2,t,ri,0)(i_2,t,r_i,0)用于检验当天餐巾数是否符合要求。
由于脏餐巾每天会产生rir_i个,所以连边(s,i1,ri,0)(s,i_1,r_i,0),这个是我在自己做该题时没有想出来的,我以为要从干净餐巾处把脏餐巾转移过来导致一直没有做出来QAQ
每天可以买餐巾(s,i2,inf,p)(s,i_2,inf,p)
可以把脏餐巾不洗留到明天(i1,(i+1)1,inf,0)(i_1,(i+1)_1,inf,0)
可以送快洗部或者慢洗部(i1,(i+m)2,inf,f)(i_1,(i+m)_2,inf,f)和(i1,(i+n)2,inf,s)(i_1,(i+n)_2,inf,s)
然后最小费用最大流就是答案啦。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=2005,inf=0x3f3f3f3f;
int n,p,m1,m2,f1,f2,s,t,tot=1,ans,mx;
int h[N],to[N*6],ne[N*6],flow[N*6],w[N*6],dis[N],pre[N],inq[N],liu[N];
void add(int x,int y,int z,int c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=inq[i]=0;queue<int> q;int x;dis[s]=0,liu[s]=inf,q.push(s);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(liu[x],flow[i]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;mx+=liu[t],x=t;while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t];ans+=liu[t]*w[kl],x=to[kl^1];}return 1;
}
int main()
{int x;scanf("%d%d%d%d%d%d",&n,&p,&m1,&f1,&m2,&f2);s=0,t=n*2+1;for(int i=1;i<=n;++i) {scanf("%d",&x);add(s,i,x,0),add(i+n,t,x,0),add(s,i+n,inf,p);if(i+1<=n) add(i,i+1,inf,0);if(i+m1<=n) add(i,n+i+m1,inf,f1);if(i+m2<=n) add(i,n+i+m2,inf,f2);}while(bfs());printf("%d",ans);return 0;
}

11.航空路线问题

题目又没有说清楚,其实路上每一步都只能从东边的城市到西边的。不然跑网络流是会出现死循环的。
所以拆点,连边(11,12,2,−1)(1_1,1_2,2,-1)和(n1,n2,2,−1)(n_1,n_2,2,-1),其他的每个i1i_1向i2i_2连边(i1,i2,1,−1)(i_1,i_2,1,-1),对于每条图上有的边(x,y)(x,y),(x小于y)连边(x2,y1,inf,0)(x_2,y_1,inf,0)。跑完最小费用最大流后,要把答案取相反数(因为连边用的费用是负数嘛)
然后输出方案看代码吧,其实就是看哪些边上有流量,求两次即可。

#include<bits/stdc++.h>
using namespace std;
map<string,int> mp;
const int N=205,M=205*205*2,inf=0x3f3f3f3f;
string name[105];
int n,m,tot=1,s,t,mx,ans;
int h[N],to[M],flow[M],ne[M],w[M],dis[N],pre[N],liu[N],inq[N];
void add(int x,int y,int z,int c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=liu[i]=0;queue<int> q;int x;dis[s]=0,liu[s]=inf,q.push(s);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(liu[x],flow[i]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;x=t,mx+=liu[t];while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t];ans+=w[kl]*liu[t],x=to[kl^1];}return 1;
}
void find1(int x) {cout<<name[x]<<endl;if(x==n) return;for(int i=h[x+n];i;i=ne[i])if(w[i]==0&&flow[i^1]!=0&&to[i]<=n) {flow[i^1]=0,find1(to[i]);return;}
}
void find2(int x) {if(x==n) return;for(int i=h[x+n];i;i=ne[i])if(w[i]==0&&flow[i^1]!=0&&to[i]<=n){find2(to[i]);break;}cout<<name[x]<<endl;
}
int main()
{string x,y;scanf("%d%d\n",&n,&m);s=0,t=n*2+1;for(int i=1;i<=n;++i) cin>>x,mp[x]=i,name[i]=x;for(int i=1;i<=m;++i) {cin>>x>>y;if(mp[x]>mp[y]) swap(x,y);add(mp[x]+n,mp[y],inf,0);}add(s,1,2,0),add(n+n,t,2,0),add(1,n+1,2,-1),add(n,n+n,2,-1);for(int i=2;i<n;++i) add(i,i+n,1,-1);while(bfs());if(mx!=2) {puts("No Solution!");return 0;}ans=-ans-2;printf("%d\n",ans);find1(1),find2(1);return 0;
}

12.软件补丁问题

震惊!网络流24题中竟有如此多不是网络流的题目,这到底是道德的沦丧还是人性的扭曲?
好吧,这题其实是一个可爱的最短路问题,一个bug状态就是一个点。由于边数比较多,所以不预先建边,而是在跑spfa的过程中建边。熟悉位运算的人应该不难写出转移方法。

#include<bits/stdc++.h>
using namespace std;
const int N=2097152,inf=0x3f3f3f3f;
int n,m,tot;
int bin[25],dis[N],inq[N];
int w[105],b1[105],b2[105],f1[105],f2[105];
void spfa() {int x,y;queue<int> q;for(int i=0;i<bin[n+1];++i) dis[i]=inf;dis[bin[n+1]-1]=0,q.push(bin[n+1]-1);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=1;i<=m;++i) {if((x&b2[i])||((x&b1[i])!=b1[i])) continue;y=x^(x&f1[i]),y|=f2[i];if(dis[x]+w[i]<dis[y]) {dis[y]=dis[x]+w[i];if(!inq[y]) inq[y]=1,q.push(y);}}}if(dis[0]<inf) printf("%d\n",dis[0]);else puts("0");
}
int main()
{char s[33];scanf("%d%d",&n,&m);bin[1]=1;for(int i=2;i<=n+1;++i) bin[i]=bin[i-1]<<1;for(int i=1;i<=m;++i) {scanf("%d",&w[i]);scanf("%s",s);for(int j=1;j<=n;++j) if(s[j-1]=='-') b2[i]|=bin[j];else if(s[j-1]=='+') b1[i]|=bin[j];scanf("%s",s);for(int j=1;j<=n;++j)if(s[j-1]=='+') f2[i]|=bin[j];else if(s[j-1]=='-') f1[i]|=bin[j];}spfa();return 0;
}

13.星际转移问题

思路:枚举+并查集+最大流
用并查集判断是否有解法。将一艘飞船可以到达的所有星球并查集连起来,最后如果地球和月球无法连接,则无解。
然后枚举答案。
所有的点为“第i个星际站在第t秒”这样一个状态的点,那么枚举的答案每增加1,就需要新建“一套”地球和太空站的点。
源点向每一个“地球”连一条容量为inf的边,每个空间站向下一时间的该空间站连一条容量为inf的边,代表时间间的转移。
每个飞船现在在哪个星球,下一秒会飞到哪一个星球都可以计算得到,所以直接连边,容量为飞船载人量。
月球就是汇点。
然后跑最大流,如果最大流大于需要转移的人数了,那么就得到了解。枚举然后每次建立新边,在残量网络上跑最大流,反而比二分答案后建立新图重跑最大流快。

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f,M=1000005;
int n,m,k,s,t,tot=1,ans,mx;
int f[100],p[100],g[100][100],num[100];
int ne[M],to[M],h[M],flow[M],lev[M],q[M];
int find(int x) {if(f[x]==x) return x;f[x]=find(f[x]);return f[x];
}
void uni(int x,int y) {x=find(x),y=find(y);if(x!=y) f[x]=y;
}
void add(int x,int y,int z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {if(x==t) return liu;int kl,sum=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&lev[to[i]]==lev[x]+1) {kl=dfs(to[i],min(flow[i],liu-sum));sum+=kl,flow[i]-=kl,flow[i^1]+=kl;if(sum==liu) return sum;}return sum;
}
int bfs() {for(int i=1;i<=ans*(n+1);++i) lev[i]=0;int he=1,ta=1,x;lev[t]=0,q[1]=s;while(he<=ta) {x=q[he],++he;if(x==t) return 1;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&!lev[to[i]])lev[to[i]]=lev[x]+1,q[++ta]=to[i];}return 0;
}
int main()
{int x,y;scanf("%d%d%d",&n,&m,&k);s=0,t=M-2;for(int i=1;i<=n+2;++i) f[i]=i;for(int i=1;i<=m;++i) {scanf("%d%d",&p[i],&num[i]);for(int j=0;j<num[i];++j) {scanf("%d",&g[i][j]);if(g[i][j]==0) g[i][j]=n+1;if(g[i][j]==-1) g[i][j]=n+2;if(j!=0) uni(g[i][j-1],g[i][j]);}}if(find(n+1)!=find(n+2)) {puts("0");return 0;}for(ans=1;;++ans) {add(s,(ans-1)*(n+1)+n+1,inf);for(int i=1;i<=m;++i) {x=(ans-1)%num[i],y=ans%num[i];if(g[i][x]==n+2) x=t;else x=(ans-1)*(n+1)+g[i][x];if(g[i][y]==n+2) y=t;else y=ans*(n+1)+g[i][y];add(x,y,p[i]);}while(bfs()) mx+=dfs(s,inf);if(mx>=k) {printf("%d\n",ans);return 0;}for(int i=1;i<=n+1;++i) add((ans-1)*(n+1)+i,ans*(n+1)+i,inf);}return 0;
}

14.孤岛营救问题

这和12题一样是一个最短路问题,同样也是状态建图。
用(所在位置,所持钥匙集合)作为一个点,然后判断一下点之间的转移进行最短路即可。

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m,p,T,tot,ans=inf;
int bin[14];
int l[105][105],key[105],dis[105][2048],inq[105][2048];
int mvx[7]={0,0,0,1,-1},mvy[7]={0,1,-1,0,0};
struct node{int x,y,have;};
void spfa() {for(int i=1;i<=n*m;++i)for(int j=0;j<bin[p+1];++j) dis[i][j]=inf;queue<node>q; node u,v;int xx,tx,ty,yy;dis[1][key[1]]=0,q.push((node){1,1,key[1]});while(!q.empty()) {u=q.front(),xx=(u.x-1)*m+u.y;q.pop(),inq[xx][u.have]=0;for(int i=1;i<=4;++i) {tx=u.x+mvx[i],ty=u.y+mvy[i],yy=(tx-1)*m+ty;if(tx<1||tx>n||ty<1||ty>m) continue;if(l[xx][yy]==0) continue;if(l[xx][yy]>0&&!(u.have&bin[l[xx][yy]])) continue;v.x=tx,v.y=ty,v.have=u.have|key[yy];if(dis[xx][u.have]+1<dis[yy][v.have]) {dis[yy][v.have]=dis[xx][u.have]+1;if(!inq[yy][v.have]) inq[yy][v.have]=1,q.push(v);}}}for(int i=0;i<bin[p+1];++i) ans=min(ans,dis[n*m][i]);if(ans<inf) printf("%d\n",ans);else puts("-1");
}
int main()
{int x1,y1,x2,y2,g;scanf("%d%d%d",&n,&m,&p);bin[1]=1;for(int i=2;i<=p+1;++i) bin[i]=bin[i-1]<<1;memset(l,-1,sizeof(l));scanf("%d",&T);while(T--) {scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&g);l[(x1-1)*m+y1][(x2-1)*m+y2]=l[(x2-1)*m+y2][(x1-1)*m+y1]=g;}scanf("%d",&T);while(T--) scanf("%d%d%d",&x1,&y1,&g),key[(x1-1)*m+y1]|=bin[g];spfa();return 0;
}

15.汽车加油行驶问题

还是状态最短路问题,建图方式很巧妙以至于我这个sb一开始没有想到……
显然,状态作为点的情况下,应该是位置+剩余油量这样一个状态,不过由于细节很多容易出错:
1.对于有加油站的点,因为强制加满油,所以只有满油状态可以向周围连边
2.对于没有加油站的点,只有没油了的时候才要建立加油站,所以可以减少一些边(防止RE)
3.没油状态不可以进行位置之间的转移

#include<bits/stdc++.h>
using namespace std;
const int N=101*101*11+5,inf=0x3f3f3f3f;
int h[N],ne[N*5],to[N*5],w[N*5],dis[N],inq[N];
int mvx[7]={0,0,0,1,-1},mvy[7]={0,1,-1,0,0};
int n,kk,a,b,c,tot,ans=inf;
void add(int x,int y,int z)
{to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void spfa() {memset(dis,0x3f,sizeof(dis));int x;queue<int>q;dis[1*(kk+1)+kk]=0,q.push(1*(kk+1)+kk);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i];if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}for(int oil=0;oil<=kk;++oil) ans=min(ans,dis[n*n*(kk+1)+oil]);printf("%d\n",ans);
}
int main()
{int xx,yy,tmp;scanf("%d%d%d%d%d",&n,&kk,&a,&b,&c);for(int i=1;i<=n;++i)for(int j=1;j<=n;++j) {scanf("%d",&tmp);xx=(i-1)*n+j;for(int k=1;k<=4;++k) {int tx=i+mvx[k],ty=j+mvy[k];yy=(tx-1)*n+ty;if(tx<1||tx>n||ty<1||ty>n) continue;if(!tmp)for(int oil=1;oil<kk;++oil)//转移位置add(xx*(kk+1)+oil,yy*(kk+1)+oil-1,(mvx[k]+mvy[k]==-1)*b);add(xx*(kk+1)+kk,yy*(kk+1)+kk-1,(mvx[k]+mvy[k]==-1)*b);}if(!tmp) add(xx*(kk+1),xx*(kk+1)+kk,a+c);//建立加油站else for(int oil=0;oil<kk;++oil)//加油add(xx*(kk+1)+oil,xx*(kk+1)+kk,a);}spfa();return 0;
}

16.数字梯形问题

费用流解决路径交问题。然而这个题面又让人很迷惑,其实是(i,j)是一条路,问3中(i,j)这条路可以被多次使用而已。
那么就用拆点+费用流就可以轻松解决了。
第一问建立(s,第一行的点1_1,1,0),(最后一行的点2_2,t,1,0),(i2,j1,1,0)(i_2,j_1,1,0),(i1,i2,1,−wi)(i_1,i_2,1,-w_i)
第二问建立(s,第一行的点1_1,1,0),(最后一行的点2_2,t,inf,0),(i2,j1,1,0)(i_2,j_1,1,0),(i1,i2,inf,−wi)(i_1,i_2,inf,-w_i)
第二问建立(s,第一行的点1_1,1,0),(最后一行的点2_2,t,inf,0),(i2,j1,inf,0)(i_2,j_1,inf,0),(i1,i2,inf,−wi)(i_1,i_2,inf,-w_i)
注意以下两点(都是本蒟蒻犯的错误):
1.费用流不可以在加边后直接重跑残量网络,这样得不到答案,必须把整张图推翻重建。
2.本蒟蒻一开始把所有建图中为inf的地方都写的是2…..

#include<bits/stdc++.h>
using namespace std;
const int N=805,M=1000*12+5,inf=0x3f3f3f3f;
int n,m,tot=1,ans,mx,s,t,now;
int h[N],ne[M],to[M],flow[M],w[M];
int dis[N],pre[N],inq[N],ma[44][44],num[N],liu[N];
void add(int x,int y,int z,int c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {queue<int> q;int x;for(int i=s;i<=t;++i) pre[i]=inq[i]=0,dis[i]=inf;dis[s]=0,liu[s]=inf,q.push(s);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(flow[i],liu[x]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;x=t;while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t];ans+=w[kl]*liu[t],x=to[kl^1];}return 1;
}
void work1() {for(int i=1;i<=now;++i) add(i,now+i,1,-num[i]);for(int i=1;i<=m;++i) add(s,i,1,0);for(int i=1;i<=m+n-1;++i) add(ma[n][i]+now,t,1,0);for(int i=1;i<n;++i)for(int j=1;j<=m+i-1;++j)add(ma[i][j]+now,ma[i+1][j],1,0),add(ma[i][j]+now,ma[i+1][j+1],1,0);while(bfs()); printf("%d\n",-ans);
}
void work2() {memset(h,0,sizeof(h));tot=1,ans=0;for(int i=1;i<=now;++i) add(i,now+i,inf,-num[i]);for(int i=1;i<=m;++i) add(s,i,1,0);for(int i=1;i<=m+n-1;++i) add(ma[n][i]+now,t,inf,0);for(int i=1;i<n;++i)for(int j=1;j<=m+i-1;++j)add(ma[i][j]+now,ma[i+1][j],1,0),add(ma[i][j]+now,ma[i+1][j+1],1,0);while(bfs()); printf("%d\n",-ans);
}
void work3() {memset(h,0,sizeof(h));tot=1,ans=0;for(int i=1;i<=now;++i) add(i,now+i,inf,-num[i]);for(int i=1;i<=m;++i) add(s,i,1,0);for(int i=1;i<=m+n-1;++i) add(ma[n][i]+now,t,inf,0);for(int i=1;i<n;++i)for(int j=1;j<=m+i-1;++j)add(ma[i][j]+now,ma[i+1][j],inf,0),add(ma[i][j]+now,ma[i+1][j+1],inf,0);while(bfs()); printf("%d\n",-ans);
}
int main()
{scanf("%d%d",&m,&n);for(int i=1;i<=n;++i)for(int j=1;j<=m+i-1;++j)ma[i][j]=++now,scanf("%d",&num[now]);s=0,t=now*2+1;work1(),work2(),work3();return 0;
}

17.运输问题

可以说是很裸的费用流问题了。
源点向每个仓库连一条流量为仓库储存量的边,每个商店向汇点连一条流量为需求量的边,仓库向商店连流量为inf,费用为运输费用的边。分别跑最大费用流和最小费用流即可。

#include<bits/stdc++.h>
using namespace std;
const int N=205,M=200*200*2+205,inf=0x3f3f3f3f;
int n,m,tot=1,s,t,ans;
int h[N],to[M],ne[M],flow[M],w[M],dis[N],liu[N],pre[N],inq[N];
int a[N],b[N],c[N][N];
void add(int x,int y,int z,int c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {queue<int> q;int x;for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=0;dis[s]=0,liu[s]=inf,q.push(s);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(liu[x],flow[i]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;x=t;while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t];ans+=liu[t]*w[kl],x=to[kl^1];}return 1;
}
void work1() {for(int i=1;i<=m;++i) scanf("%d",&a[i]),add(s,i,a[i],0);for(int i=1;i<=n;++i) scanf("%d",&b[i]),add(i+m,t,b[i],0);for(int i=1;i<=m;++i)for(int j=1;j<=n;++j)scanf("%d",&c[i][j]),add(i,j+m,inf,c[i][j]);while(bfs());printf("%d\n",ans);
}
void work2() {memset(h,0,sizeof(h));tot=1,ans=0;for(int i=1;i<=m;++i) add(s,i,a[i],0);for(int i=1;i<=n;++i) add(i+m,t,b[i],0);for(int i=1;i<=m;++i)for(int j=1;j<=n;++j)add(i,j+m,inf,-c[i][j]);while(bfs());printf("%d\n",-ans);
}
int main()
{scanf("%d%d",&m,&n);s=0,t=m+n+1;work1(),work2();return 0;
}

18.分配问题

这…..这不是和上一题一毛一样的吗,直接改一改上一题代码即可啊。

#include<bits/stdc++.h>
using namespace std;
const int N=205,M=200*200*2+205,inf=0x3f3f3f3f;
int n,m,tot=1,s,t,ans;
int h[N],to[M],ne[M],flow[M],w[M],dis[N],liu[N],pre[N],inq[N];
int c[N][N];
void add(int x,int y,int z,int c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {queue<int> q;int x;for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=0;dis[s]=0,liu[s]=inf,q.push(s);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(liu[x],flow[i]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;x=t;while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t];ans+=liu[t]*w[kl],x=to[kl^1];}return 1;
}
void work1() {for(int i=1;i<=m;++i) add(s,i,1,0);for(int i=1;i<=n;++i) add(i+m,t,1,0);for(int i=1;i<=m;++i)for(int j=1;j<=n;++j)scanf("%d",&c[i][j]),add(i,j+m,inf,c[i][j]);while(bfs());printf("%d\n",ans);
}
void work2() {memset(h,0,sizeof(h));tot=1,ans=0;for(int i=1;i<=m;++i) add(s,i,1,0);for(int i=1;i<=n;++i) add(i+m,t,1,0);for(int i=1;i<=m;++i)for(int j=1;j<=n;++j) add(i,j+m,inf,-c[i][j]);while(bfs());printf("%d\n",-ans);
}
int main()
{scanf("%d",&n);m=n;s=0,t=m+n+1;work1(),work2();return 0;
}

19.负载平衡问题

非常明显的费用流建图方法,对于多于平均值xx的物品,建一条从源点到这个点的边(s,i,ai−x,0)(s,i,a_i-x,0),否则建边(i,t,x−ai,0)(i,t,x-a_i,0)。然后互相建边(i,i+1,inf,1)和(i-1,i,inf,1)

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f,N=105,M=2005;
int n,s,t,sum,ans,tot=1;
int h[N],to[M],ne[M],flow[M],w[M],inq[N],liu[N],pre[N],dis[N],a[N];
void add(int x,int y,int z,int c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {queue<int>q;int x;for(int i=s;i<=t;++i) pre[i]=inq[i]=0,dis[i]=inf;liu[s]=inf,q.push(s),dis[s]=0;while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(liu[x],flow[i]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;x=t;while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t];ans+=w[kl]*liu[t],x=to[kl^1];}return 1;
}
int main()
{scanf("%d",&n);s=0,t=n+1;for(int i=1;i<=n;++i) scanf("%d",&a[i]),sum+=a[i];sum/=n;for(int i=1;i<=n;++i) {if(a[i]>sum) add(s,i,a[i]-sum,0);else add(i,t,sum-a[i],0);if(i!=1) add(i,i-1,inf,1);else add(i,n,inf,1);if(i!=n) add(i,i+1,inf,1);else add(i,1,inf,1);}while(bfs());printf("%d",ans);return 0;
}

20.深海机器人问题

费用流的建图还是比较明显的,因为只有一个机器人能捡起标本,所以两个点之间应该连两条路径,一条为(x,y,1,-c)一条为(x,y,inf,0)

#include<bits/stdc++.h>
using namespace std;
const int N=20*20+5,M=20*20*6+5,inf=0x3f3f3f3f;
int a,b,n,m,tot=1,ans,s,t;
int h[N],to[M],ne[M],flow[M],w[M],dis[N],liu[N],pre[N],inq[N];
void add(int x,int y,int z,int c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=inq[i]=0;queue<int> q;int x;dis[s]=0,liu[s]=inf,q.push(s);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(liu[x],flow[i]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;x=t;while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t];ans+=liu[t]*w[kl],x=to[kl^1];}return 1;
}
int main()
{int x,y,k;scanf("%d%d%d%d",&a,&b,&n,&m);s=0,t=(n+1)*(m+1)+1;for(int i=1;i<=n+1;++i)for(int j=1;j<=m;++j) {scanf("%d",&x);add((m+1)*(i-1)+j,(m+1)*(i-1)+j+1,1,-x);add((m+1)*(i-1)+j,(m+1)*(i-1)+j+1,inf,0);}for(int i=1;i<=m+1;++i)for(int j=1;j<=n;++j) {scanf("%d",&x);add((m+1)*(j-1)+i,(m+1)*j+i,1,-x);add((m+1)*(j-1)+i,(m+1)*j+i,inf,0);}for(int i=1;i<=a;++i)scanf("%d%d%d",&k,&x,&y),add(s,x*(m+1)+y+1,k,0);for(int i=1;i<=b;++i)scanf("%d%d%d",&k,&x,&y),add(x*(m+1)+y+1,t,k,0);while(bfs());printf("%d",-ans);return 0;
}

21.最长k可重区间集问题

一个巧妙的费用流问题(本蒟蒻真做不出QAQ)。
我们需要解决的问题:
1.控制每个点被选的次数:可以想到这个可以通过流量来控制。
2.计算区间长度对答案带来的价值:可以想到用费用来搞。
所以得到了一个建图方法:离散端点(记得要去重!)后,将每个端点作为一个点,连边(i,i+1,inf,0)(i,i+1,inf,0)。设tttt为最后一个端点,那么连边(s,1,k,0)(s,1,k,0)和(tt,t,k,0)(tt,t,k,0)。最后,如果有一个区间长度为ll,连边(l,r,1,−l)(l,r,1,-l)
这样,如果一个点作为坐端点,引一个单位的流量走了另一条路,那么大于左端点小于右端点的点们,能引出的区间个数就会减少1个(画个图就可以理解啦),保证了选点次数的限制不被打破。

#include<bits/stdc++.h>
using namespace std;
const int N=1005,M=5005,inf=0x3f3f3f3f;
int n,m,s,t,ans,tot=1,tt;
int h[N],ne[M],to[M],w[M],flow[M];
int dis[N],liu[N],pre[N],inq[N],b[N],l[N],r[N];
void add(int x,int y,int z,int c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {for(int i=s;i<=t;++i) dis[i]=inf,inq[i]=pre[i]=0;queue<int>q;int x; dis[s]=0,liu[s]=inf,q.push(s);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(liu[x],flow[i]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;x=t;while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t];ans+=liu[t]*w[kl],x=to[kl^1];}return 1;
}
int main()
{int x,y;scanf("%d%d",&n,&m);s=0,t=2*n+1;for(int i=1;i<=n;++i) {scanf("%d%d",&l[i],&r[i]);if(l[i]>r[i]) swap(l[i],r[i]);b[i]=l[i],b[i+n]=r[i];}sort(b+1,b+1+2*n);tt=1;for(int i=2;i<=2*n;++i) if(b[i]!=b[tt]) b[++tt]=b[i];add(s,1,m,0),add(tt,t,m,0);for(int i=1;i<tt;++i) add(i,i+1,inf,0);for(int i=1;i<=n;++i) {x=lower_bound(b+1,b+1+tt,l[i])-b;y=lower_bound(b+1,b+1+tt,r[i])-b;add(x,y,1,l[i]-r[i]);}while(bfs());printf("%d",-ans);return 0;
}

22.最长k可重线段集问题

网上所有题解都说和上一题差不多。。。
于是我就按照上一题的写法写了一下,TLE。。。
然后我看了loj上的AC代码,发现我忘记判断与x轴垂直的直线了,可能会连出自环导致跑费用流时死循环(注释部分)。改了之后顺利在loj上AC。。。
然后兴奋地跑到别的oj上去交,都是WA。。。
现在整个人都迷茫了。。。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1005,M=200005;const LL inf=1e14;
int n,m,s,t,tot=1,tt;LL ans;
int h[N],ne[M],to[M];LL w[M],flow[M];
LL dis[N],liu[N],b[N];LL pre[N],inq[N];
struct node{LL x,y;} p1[N],p2[N];
void add(int x,int y,LL z,LL c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {for(int i=s;i<=t;++i) dis[i]=inf,inq[i]=pre[i]=0;queue<int>q;int x; dis[s]=0,liu[s]=inf,q.push(s);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(liu[x],flow[i]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;x=t;while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t];ans+=liu[t]*w[kl],x=to[kl^1];}return 1;
}
int main()
{int x,y,v;scanf("%d%d",&n,&m);s=0,t=2*n+1;for(int i=1;i<=n;++i) {scanf("%lld%lld%lld%lld",&p1[i].x,&p1[i].y,&p2[i].x,&p2[i].y);if(p1[i].x>p2[i].x) swap(p1[i],p2[i]);b[i]=p1[i].x,b[i+n]=p2[i].x;}sort(b+1,b+1+2*n);tt=1;for(int i=2;i<=2*n;++i) if(b[i]!=b[tt]) b[++tt]=b[i];add(s,1,m,0),add(tt,t,m,0);for(int i=1;i<tt;++i) add(i,i+1,inf,0);for(int i=1;i<=n;++i) {x=lower_bound(b+1,b+1+tt,p1[i].x)-b;y=lower_bound(b+1,b+1+tt,p2[i].x)-b;v=(LL)sqrt((p1[i].x-p2[i].x)*(p1[i].x-p2[i].x)+(p1[i].y-p2[i].y)*(p1[i].y-p2[i].y));if(x==y) ++y;//注意这里,避免死循环add(x,y,1,-v);}while(bfs());printf("%lld",-ans);return 0;
}

23.火星探险问题

类似于第20题的一个费用流问题。由于20题标本在边上,所以在边上控制费用。而此题标本在点上,所以采用拆点的思想,如果一个点上有标本,连边(i1,i2,1,−1)(i_1,i_2,1,-1)和(i1,i2,inf,0)(i_1,i_2,inf,0),否则只用连(i1,i2,inf,0)(i_1,i_2,inf,0)。相连的节点i和j之间再连边(i2,j1,inf,0)(i_2,j_1,inf,0)即可。
至于输出方案,就是去检索残量网络的一个过程,可以通过检索反向边是否有流量来得到这条边是否被一辆车走过了。

#include<bits/stdc++.h>
using namespace std;
const int N=35*35*2+5,M=35*35*10+5,inf=0x3f3f3f3f;
int h[N],to[M],ne[M],flow[M],w[M],liu[N],dis[N],pre[N],inq[N];
int ma[40][40];
int car,n,m,s,t,tot=1;
void add(int x,int y,int z,int c) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {queue<int>q;int x;for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=inq[i]=0;liu[s]=inf,dis[s]=0,q.push(s);while(!q.empty()) {x=q.front(),q.pop(),inq[x]=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;liu[to[i]]=min(liu[x],flow[i]);if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);}}if(!pre[t]) return 0;x=t;while(x!=s) {int kl=pre[x];flow[kl]-=liu[t],flow[kl^1]+=liu[t],x=to[kl^1];}return 1;
}
void print(int x,int pos) {//输出方案if(x==n*m*2) return;for(int i=h[x];i;i=h[x]) {if(flow[i^1]>0&&to[i]<=n*m) {//如果这条边被车走过了printf("%d ",pos);if(to[i]==x-n*m+1) puts("1");else puts("0");--flow[i^1],print(to[i]+n*m,pos);return;}h[x]=ne[i];}
}
int main()
{int x,tmp;scanf("%d%d%d",&car,&m,&n);s=0,t=n*m*2+1;add(s,1,car,0),add(n*m+n*m,t,car,0);for(int i=1;i<=n;++i)for(int j=1;j<=m;++j) scanf("%d",&ma[i][j]);for(int i=1;i<=n;++i)for(int j=1;j<=m;++j) {tmp=(i-1)*m+j;if(ma[i][j]==1) continue;if(ma[i][j]==2) add(tmp,tmp+n*m,1,-1);add(tmp,tmp+n*m,inf,0);if(j!=m&&ma[i][j+1]!=1) add(tmp+n*m,tmp+1,inf,0);if(i!=n&&ma[i+1][j]!=1) add(tmp+n*m,tmp+m,inf,0);}while(bfs());for(int i=1;i<=car;++i) print(n*m+1,i);return 0;
}

24.骑士共存问题

也是一个最大独立点集的问题,参见第9题。
不过这题的最后一个测试点很BT,要在dinic跑dfs的时候判断,如果在某个点上的流量等于0了,这次增广就不要再到这个点来了(也就是代码注明部分)

#include<bits/stdc++.h>
using namespace std;
const int N=200*200+5,M=200*200*30+5,inf=0x3f3f3f3f;
int n,m,s,t,tot=1,ans;
int mvx[10]={0,-2,-1,1,2},mvy[10]={0,1,2,2,1};
int ok[205][205],h[N],ne[M],to[M],flow[M],lev[N],q[N];
void add(int x,int y,int z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {if(x==t) return liu;int kl,sum=0;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&lev[to[i]]==lev[x]+1) {kl=dfs(to[i],min(liu-sum,flow[i]));flow[i]-=kl,flow[i^1]+=kl,sum+=kl;if(sum==liu) return sum;}if(!sum) lev[x]=-1;//look at here. this is a 优化return sum;
}
int bfs() {for(int i=s;i<=t;++i) lev[i]=0;int he=1,ta=1,x;lev[s]=1,q[1]=s;while(he<=ta) {x=q[he],++he;if(x==t) return 1;for(int i=h[x];i;i=ne[i])if(flow[i]>0&&!lev[to[i]])lev[to[i]]=lev[x]+1,q[++ta]=to[i];}return 0;
}
int main()
{int x,y;scanf("%d%d",&n,&m);s=0,t=n*n+1;for(int i=1;i<=m;++i) scanf("%d%d",&x,&y),ok[x][y]=1;for(int i=1;i<=n;++i)for(int j=1;j<=n;++j) {if(ok[i][j]) continue;if((i+j)&1) add(s,(i-1)*n+j,1);else add((i-1)*n+j,t,1);for(int k=1;k<=4;++k) {int tx=i+mvx[k],ty=j+mvy[k];if(tx<1||tx>n||ty<1||ty>n||ok[tx][ty]) continue;if((i+j)&1) add((i-1)*n+j,(tx-1)*n+ty,inf);else add((tx-1)*n+ty,(i-1)*n+j,inf);}}while(bfs()) ans+=dfs(s,inf);printf("%d",n*n-m-ans);return 0;
}

蒟蒻的网络流24题解题记相关推荐

  1. 题解 【网络流24题】太空飞行计划

    [网络流24题]太空飞行计划 Description W 教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合E={E1,E2,- ...

  2. USACO 简易题解(蒟蒻的题解)

    蒟蒻难得可以去比赛,GDOI也快到了,还是认真刷题(不会告诉你之前都在颓废),KPM 神犇既然都推荐刷USACO, 辣就刷刷. 现在蒟蒻还没刷完,太蒟刷得太慢,so 写了的搞个简易题解(没代码,反正N ...

  3. 【题解】网络流24题一句话题解集合

    最近写了下<线性规划与网络流24题>,发下代码和题解,事实上就是将交给cycycy的题解复制一下 T1 飞行员配对方案问题 solution 裸的匈牙利 code #include< ...

  4. 蒟蒻君的刷题日记Day12(线段树专题T4):P8082 [COCI2011-2012#4] KEKS 线段树版题解

    解题思路 看题解区的大佬们用的都是单调栈,本蒟蒻献上一篇线段树题解. 整个数最大,首先位数是确定的,则肯定优先考虑高位大小. 大体思路就是从前向后依次求出每一位的值(好像是废话). 对于第 iii 位 ...

  5. 【hjmmm网络流24题补全计划】

    本文食用方式 按ABC--分层叙述思路 可以看完一步有思路后自行思考 飞行员配对问题 题目链接 这可能是24题里最水的一道吧... 很显然分成两个集合 左外籍飞行员 右皇家飞行员 跑二分图最大匹配 输 ...

  6. 【网络流24题补全计划】

    本文食用方式 按ABC--分层叙述思路 可以看完一步有思路后自行思考 飞行员配对问题 题目链接 这可能是24题里最水的一道吧... 很显然分成两个集合 左外籍飞行员 右皇家飞行员 跑二分图最大匹配 输 ...

  7. [颓废史]蒟蒻的刷题记录

    QAQ蒟蒻一枚,其实我就是来提供水题库的. 以下记录从2016年开始. 1.1 1227: [SDOI2009]虔诚的墓主人 树状数组+离散化 3132: 上帝造题的七分钟 树状数组 二维区间加减+查 ...

  8. 蒟蒻成长之路(持续更新)

    蒟蒻成长之路 (这个玩意只是闲着写写, 写给自己看的) 开始 开始日期:2023年3月23日20:55:24 内容 主要记录一些做题日常和快乐的学校生活 初一:2022~2023 Day1--2023 ...

  9. 解题报告:线性规划与网络流24题

    目录 A.飞行员配对方案问题 (二分图最大匹配)(最大流)[提高+/省选- ] B.太空飞行计划问题(最大权闭合图转最小割.最小割方案输出)[省选/NOI- ] C.最小路径覆盖问题(有向无环图最小路 ...

  10. 【bzoj4916】神犇和蒟蒻 杜教筛

    题目描述 很久很久以前,有一只神犇叫yzy; 很久很久之后,有一只蒟蒻叫lty; 输入 请你读入一个整数N;1<=N<=1E9,A.B模1E9+7; 输出 请你输出一个整数A=\sum_{ ...

最新文章

  1. 『网站升级』PHPWind8.0至8.3升级过程及问题种种回顾录
  2. Linux如何在系统启动时自动加载模块
  3. JZOJ 5281. 【NOIP提高组模拟A组8.15】钦点
  4. ADO.NET的记忆碎片(六)
  5. java向注册表单传递数据php_PHP提交from表单的方法
  6. LS 24 Bracket sequence(DP)
  7. 异常:Invalid or unexpected token
  8. 【数据预测】基于matlab鸟群算法优化BP神经网络数据预测【含Matlab源码 1772期】
  9. 知网显示html,使用知网HTML阅读的正确姿势
  10. win10安装vc2015失败,尝试解决方案,目前有效
  11. 干货分享 | 中国地理分界线归纳及高清地图!
  12. 五大领域总目标指南_幼儿园五大领域总目标
  13. 电脑连android手机上网,电脑通过手机3G上网(android安卓手机)的几种方法
  14. 8位并行左移串行转换电路_单片机试题
  15. 三维空间刚体运动——(1)齐次坐标与旋转矩阵
  16. JS控制DIV的显示隐藏
  17. 【C++】-- 友元
  18. STM32 烧录程序后上电不工作,但调试模式下可正常工作的解决办法
  19. 样式和主题(Style and Theme)详解
  20. C语言_回文字符串的判断

热门文章

  1. LOJ #3049. 「十二省联考 2019」字符串问题
  2. Ubuntu 部署 Flask + WSGI + Nginx 详解
  3. Linux Minit Xshell5连接虚拟机Minit
  4. android 手机 对比,看!Android平台三款手机浏览器对比评测
  5. 如何实现打开网页自动弹出QQ对话框
  6. C++学习(11)(综合题)
  7. 学1个月爬虫就月赚6000?告诉你爬虫的真实情况!
  8. 那一年我是如何从功能测试跨入自动化测试的,绝对让你不虚此行!
  9. Learning Center Probability Map for Detecting Objects in Aerial Images 论文学习笔记
  10. Centos 7 配置双网卡