蒟蒻的网络流24题解题记
来源:互联网 发布:淘宝访客只有二三十 编辑:程序博客网 时间:2024/06/12 21:23
食用手册
网络流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的递增序列(不可重叠),最多可以选多少个。第三问中不可以连续使用多个
这题的建图很巧妙,给跪了。
首先用
将每一个点拆成
求一遍最大流得到第二问的解。
如果存在
#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
还是很明显的,首先要把一天拆成两个点,一个表示当天过后脏餐巾
由于干净餐巾需要
由于脏餐巾每天会产生
每天可以买餐巾
可以把脏餐巾不洗留到明天
可以送快洗部或者慢洗部
然后最小费用最大流就是答案啦。
#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.航空路线问题
题目又没有说清楚,其实路上每一步都只能从东边的城市到西边的。不然跑网络流是会出现死循环的。
所以拆点,连边
然后输出方案看代码吧,其实就是看哪些边上有流量,求两次即可。
#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,第一行的点
第二问建立(s,第一行的点
第二问建立(s,第一行的点
注意以下两点(都是本蒟蒻犯的错误):
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.负载平衡问题
非常明显的费用流建图方法,对于多于平均值
#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.计算区间长度对答案带来的价值:可以想到用费用来搞。
所以得到了一个建图方法:离散端点(记得要去重!)后,将每个端点作为一个点,连边
这样,如果一个点作为坐端点,引一个单位的流量走了另一条路,那么大于左端点小于右端点的点们,能引出的区间个数就会减少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 longconst 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题标本在边上,所以在边上控制费用。而此题标本在点上,所以采用拆点的思想,如果一个点上有标本,连边
至于输出方案,就是去检索残量网络的一个过程,可以通过检索反向边是否有流量来得到这条边是否被一辆车走过了。
#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题解题记
- 网络流24题做题记录(更新中)
- 网络流刷题记录-最大流
- 网络流刷题记录-最小割
- [颓废史]蒟蒻的刷题记录
- 网络流24题解题报告小结
- 线性规划与网络流 24 题 题解
- 网络流24题一句话题解集合
- 网络流题解分布
- [网络流]matrix题解
- 网络流 某些题解
- [网络流] 网络流(22/24)题题解集合
- Leetcode Database 18道题解题记录
- HDU 3605 网络流题解
- 【网络流24题】----题解(部分,持续更新...)
- 网络流24题 题解 (部分) 更新中
- 网络流24题之 飞行员配对方案问题 题解
- Dinic 网络流24题 圆桌问题 题解
- 利用带花树算法解决一般图的最大匹配
- WebSocket实例教程
- 利用python进行数据分析学习笔记—python基础知识
- 把Hive操作的spark代码丢到yarn上面运行找不到数据库
- KDD1999 数据集
- 蒟蒻的网络流24题解题记
- Studio真机测试
- linux 基本分区管理——分区、格式化、挂载
- JDBC为什么使用反射加载驱动
- Map集合中value()方法与keySet()、entrySet()区别
- 【Java】在一个字符串指定位置插入字符串
- 【论文笔记】物体检测系列 Light-Head R-CNN: In Defense of Two-Stage Object Detector
- Qt工程文件知多少
- connect by level 的小应用