网络流24题解题总结(更新中)
2018-01-19 11:25
453 查看
目录
目录前言
最大流问题
飞行员配对方案问题
最小路径覆盖问题
魔术球问题
圆桌问题
最长不下降子序列问题
试题库问题
星际转移问题
最小割问题
太空飞行计划问题
方格取数问题
费用流问题
餐巾计划问题
航空路线问题
软件补丁问题
数字梯形问题
运输问题
分配问题
负载平衡问题
深海机器人问题
最长k可重区间集问题
最长k可重线段集问题
不可做问题
机器人路径规划问题
前言
从今天(2018.1.19)起,本人要在这篇文章中持续更新网络流24题的解法。注意,本人这里贴的做法和代码都以洛谷上的题目版本为准,可能与其他OJ的版本略有出入,所以请自行判断。
为了讨论方便,我们约定以下符号:
S,TS,T:源点和汇点。
(u,v,maxf,c)(u,v,maxf,c):表示从uu到vv连一条容量为maxfmaxf,费用为cc的边。有时费用为00时省略cc。
<u,v,minf,maxf,c><u,v,minf,maxf,c>:表示从uu到vv连一条流量下界为minfminf,上界为maxfmaxf,费用为cc的边。有时费用为00时省略cc。
那么,现在就开始吧。
最大流问题
飞行员配对方案问题
更新时间: 2018.1.19测试地址:飞行员配对方案问题
做法:本题需要用到最大流。
这个题目是很经典的二分图最大匹配的模型了,用匈牙利算法也可以做,但既然出现在网络流24题里,那么就用最大流做即可。具体做法应该不用多说,从SS到所有外籍飞行员连边,从所有英国飞行员到TT连边,再在可以配合的飞行员之间连边(从外籍到英国),容量都为11,然后跑最大流即可。
至于输出方案,显然可知,在我们连的正向边中满流的边就是匹配边,所以一个循环搞定。
以下是本人代码:
#include <bits/stdc++.h> #define inf 1000000000 using namespace std; int m,n,a,b,first[210]={0},tot=1,lvl[210]; struct edge {int v,next,f;} e[100010]; queue <int> Q; void insert(int a,int b,int f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot; } bool makelevel() { lvl[0]=0; for(int i=1;i<=m+n+1;i++) lvl[i]=-1; Q.push(0); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; Q.push(e[i].v); } } return lvl[m+n+1]!=-1; } int maxflow(int v,int maxf) { if (v==m+n+1) return maxf; int ret=0,f; for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(e[i].f,maxf-ret)); e[i].f-=f; e[i^1].f+=f; ret+=f; if (ret==maxf) break; } return ret; } int dinic() { int maxf=0; while(makelevel()) maxf+=maxflow(0,inf); return maxf; } int main() { scanf("%d%d",&m,&n); for(int i=1;i<=m;i++) insert(0,i,1); for(int i=1;i<=n;i++) insert(m+i,m+n+1,1); while(scanf("%d%d",&a,&b)&&a!=-1) insert(a,b,1); printf("%d\n",dinic()); for(int i=1;i<=m;i++) for(int j=first[i];j;j=e[j].next) if (!e[j].f&&j%2==0) printf("%d %d\n",i,e[j].v); return 0; }
最小路径覆盖问题
[b]更新时间: 2018.1.20测试地址:最小路径覆盖问题
做法:本题需要用到最大流。
求有向无环图的最小路径覆盖,可以转化为二分图最大匹配的模型求解。将每个点拆成两个点xi,yixi,yi,然后如果原图存在有向边ii->jj,则在二分图中连yiyi->xjxj。注意到,对于该二分图的任意一个匹配,将所有xi,yixi,yi缩成一个点后,就等价于一个路径覆盖,而且覆盖的路径条数==点数−−匹配数。那么显然,当匹配数最大时,覆盖的路径条数自然就最小,那么我们就得到了一个最小的路径覆盖。因此只用对上面构造出的二分图求最大匹配,可以用最大流解决。输出方案应该也很容易,只需要从路径的开头沿着匹配边走就可以了。
以下是本人代码:
#include<bits/stdc++.h> #define inf 1000000000 using namespace std; int n,m,first[310]={0},tot=1,lvl[310]={0},path[160]={0}; struct edge {int v,next,f;} e[100010]; queue <int> Q; bool vis[160]={0},in[160]={0}; void insert(int a,int b,int f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot; } bool makelevel() { lvl[0]=0; for(int i=1;i<=2*n+1;i++) lvl[i]=-1; Q.push(0); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; Q.push(e[i].v); } } return lvl[2*n+1]!=-1; } int maxflow(int v,int maxf) { if (v==2*n+1) return maxf; int ret=0,f; for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(e[i].f,maxf-ret)); e[i].f-=f; e[i^1].f+=f; ret+=f; if (ret==maxf) break; } return ret; } int dinic() { int maxf=0; while(makelevel()) maxf+=maxflow(0,inf); return maxf; } void output() { for(int i=1;i<=n;i++) for(int j=first[2*i-1];j;j=e[j].next) if (j%2==0&&!e[j].f) in[e[j].v/2]=1; for(int i=1;i<=n;i++) if (!in[i]) { path[0]=0; int x=2*i-1; bool flag=1; while(flag) { flag=0; path[++path[0]]=(x+1)/2; vis[(x+1)/2]=1; for(int j=first[x];j;j=e[j].next) if (j%2==0&&!e[j].f) {x=e[j].v-1;flag=1;break;} } for(int j=path[0];j>=1;j--) printf("%d ",path[j]); printf("\n"); } for(int i=first[0];i;i=e[i].next) if (!vis[(e[i].v+1)/2]) printf("%d\n",(e[i].v+1)/2); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int a,b; scanf("%d%d",&a,&b); insert(2*b-1,2*a,1); } for(int i=1;i<=n;i++) { insert(0,2*i-1,1); insert(2*i,2*n+1,1); } int ans=n-dinic(); output(); printf("%d",ans); return 0; }
魔术球问题
[b]更新时间: 2018.1.20测试地址:魔术球问题
做法:本题需要用到最大流。
我们想办法把该题化成我们见过的模型解决。我们很快想到,需要从小到大枚举球的个数,然后化为判定性问题:能不能用不超过nn根柱子放下这些球?可以看出,球ii和j(i<j)j(i<j)如果能放在一起,那么就相当于存在有向边ii->jj,那么一根柱子就相当于一条路径,因此只需对这样构造出的有向图求一个最小路径覆盖,如果路径数不超过nn就表示能放。求最小路径覆盖我们就很熟悉了,分析详见上面一题。
以下是本人代码:
#include<bits/stdc++.h> #define inf 1000000000 #define s 0 #define t 4009 using namespace std; int n,m,first[4010]={0},tot=1,lvl[4010]={0},saved=0; struct edge {int v,next,f;} e[400010]; queue <int> Q; bool vis[2010]={0},in[2010]={0}; void insert(int a,int b,int f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot; } bool makelevel() { lvl[s]=0; for(int i=1;i<=m;i++) lvl[i]=-1; lvl[t]=-1; Q.push(0); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&(e[i].v<=m||e[i].v==t)&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; Q.push(e[i].v); } } return lvl[t]!=-1; } int maxflow(int v,int maxf) { if (v==t) return maxf; int ret=0,f; for(int i=first[v];i;i=e[i].next) if (e[i].f&&(e[i].v<=m||e[i].v==t)&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(e[i].f,maxf-ret)); e[i].f-=f; e[i^1].f+=f; ret+=f; if (ret==maxf) break; } return ret; } int dinic() { int maxf=saved; while(makelevel()) maxf+=maxflow(0,inf); saved=maxf; return maxf; } void output() { for(int i=1;i<=m/2;i++) for(int j=first[2*i-1];j;j=e[j].next) if (j%2==0&&!e[j].f) in[e[j].v/2]=1; for(int i=1;i<=m/2;i++) if (!in[i]&&!vis[i]) { int x=2*i-1; bool flag=1; while(flag) { flag=0; printf("%d ",(x+1)/2); vis[(x+1)/2]=1; for(int j=first[x];j;j=e[j].next) if (j%2==0&&!e[j].f) {x=e[j].v-1;flag=1;break;} } printf("\n"); } for(int i=first[0];i;i=e[i].next) if (!vis[(e[i].v+1)/2]&&e[i].v<=m) printf("%d\n",(e[i].v+1)/2); } bool check(int now) { m=2*now; insert(s,2*now-1,1); insert(2*now,t,1); int j=1; for(int i=1;i<now;i++) { while(j*j<i+now) j++; if (j*j==i+now) insert(2*i-1,2*now,1); } int ans=now-dinic(); return ans<=n; } int main() { scanf("%d",&n); int i=1; while(check(i)) i++; i--; m=2*i; saved=0; for(int i=2;i<=tot;i+=2) e[i].f=1,e[i+1].f=0; dinic(); printf("%d\n",i); output(); return 0; }
圆桌问题
[b]更新时间: 2018.1.20测试地址:圆桌问题
做法:本题需要用到最大流。
本题是一个经典的二分图多重匹配的模型,显然对于每个单位ii连接(S,i,ri)(S,i,ri),对于每个餐桌jj连接(j,T,cj)(j,T,cj),又因为每个餐桌不能坐两个或者以上的同一个单位的代表,所以对于单位ii和餐桌jj之间应该连接(i,j,1)(i,j,1),建完之后跑最大流即可。输出方案的方法和前面的二分图最大匹配差不多,不同的是这里有无解的情况,如果最大流不等于所有单位代表人数总和的话,显然是无解的,其余的输出方法详见代码。
以下是本人代码:
#include <bits/stdc++.h> #define s 0 #define t m+n+1 #define inf 1000000000 using namespace std; int m,n,sum=0,first[510]={0},tot=1,lvl[510]; struct edge {int v,next,f;} e[100010]; queue <int> Q; void insert(int a,int b,int f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot; } bool makelevel() { lvl[s]=0; for(int i=1;i<=t;i++) lvl[i]=-1; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; Q.push(e[i].v); } } return lvl[t]!=-1; } int maxflow(int v,int maxf) { if (v==t) return maxf; int ret=0,f; for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(e[i].f,maxf-ret)); e[i].f-=f; e[i^1].f+=f; ret+=f; if (ret==maxf) break; } return ret; } int dinic() { int maxf=0; while(makelevel()) maxf+=maxflow(s,inf); return maxf; } void output() { for(int i=1;i<=m;i++) { for(int j=first[i];j;j=e[j].next) if (j%2==0&&!e[j].f) printf("%d ",e[j].v-m); printf("\n"); } } int main() { scanf("%d%d",&m,&n); for(int i=1;i<=m;i++) { int a; scanf("%d",&a); sum+=a; insert(s,i,a); } for(int i=1;i<=n;i++) { int a; scanf("%d",&a); insert(m+i,t,a); } for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) insert(i,m+j,1); if (dinic()!=sum) printf("0"); else { printf("1\n"); output(); } return 0; }
最长不下降子序列问题
[b]更新时间: 2018.1.21测试地址:最长不下降子序列问题
做法:本题需要用到DP+最大流。
对于第一问,我们直接O(n2)O(n2)DP求出答案即可,将答案记为KK。
对于第二问,令f(i)f(i)为以第ii个元素结尾的最长不下降子序列的长度,这个东西在上一步DP时就已经求出来了,接下来我们这样建图:如果两点i,ji,j满足关系:i<ji<j且ai≤ajai≤aj且f(i)+1=f(j)f(i)+1=f(j),则连有向边ii->jj。注意到,从任意一个f(i)=1f(i)=1的点,沿这些有向边走到一个f(i)=Kf(i)=K的点,中间的路径上经过的点就是一个长为KK的最长不下降子序列,那么问题就转化为:有若干个起点和终点,求最多能有多少条不相交的从起点到终点的路径?这个模型就可以用网络流的方法解决了。
首先,因为每个点只能出现一次,所以有“点流量”的限制,这里每个点的限制都是11。一般遇到这种情况,我们可以将一个点拆成两个点u,vu,v,并将原点的入边都连到uu,将原点的出边都连到vv,然后连接(u,v,maxf)(u,v,maxf),这里显然maxf=1maxf=1,这样就可以解决点流量限制的问题。剩下的部分应该挺容易想出来了,对于所有f(i)=1f(i)=1的点,连接(S,ui,1)(S,ui,1),对于所有f(i)=Kf(i)=K的点,连接(vi,T,1)(vi,T,1),对于原图中所有的有向边ii->jj,对应连接(vi,uj,1)(vi,uj,1),然后对这个网络跑一次最大流就是第二问的答案。
对于第三问,第11和第nn个元素可以出现多次,那么我们只需将原网络中的(S,u1,1),(u1,v1,1),(un,vn,1),(vn,T,1)(S,u1,1),(u1,v1,1),(un,vn,1),(vn,T,1)四条边的容量改为infinf,然后再跑一次最大流就是第三问的答案。
以下是本人代码:
#include<bits/stdc++.h> #define s 0 #define t 2*n+1 #define inf 1000000000 using namespace std; int n,a[510],f[510],mx=0,first[1010]={0},tot=1,lvl[1010]; struct edge {int< b0c8 /span> v,next,f;} e[200010]; queue <int> Q; void insert(int a,int b,int f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot; } bool makelevel() { lvl[s]=0; for(int i=1;i<=t;i++) lvl[i]=-1; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; Q.push(e[i].v); } } return lvl[t]!=-1; } int maxflow(int v,int maxf) { if (v==t) return maxf; int ret=0,f; for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(e[i].f,maxf-ret)); e[i].f-=f; e[i^1].f+=f; ret+=f; if (ret==maxf) break; } return ret; } int dinic() { int maxf=0; while(makelevel()) maxf+=maxflow(0,inf); return maxf; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); f[i]=1; for(int j=1;j<i;j++) if (a[j]<=a[i]) f[i]=max(f[i],f[j]+1); for(int j=1;j<i;j++) if (a[j]<=a[i]&&f[i]==f[j]+1) insert(2*j,2*i-1,1); mx=max(mx,f[i]); } for(int i=1;i<=n;i++) { insert(2*i-1,2*i,1); if (f[i]==1) insert(s,2*i-1,1); if (f[i]==mx) insert(2*i,t,1); } printf("%d\n",mx); printf("%d\n",dinic()); for(int i=2;i<=tot;i+=2) e[i].f=1,e[i+1].f=0; for(int i=first[s];i;i=e[i].next) if (i%2==0&&e[i].v==1) {e[i].f=inf;break;} for(int i=first[1];i;i=e[i].next) if (i%2==0&&e[i].v==2) {e[i].f=inf;break;} for(int i=first[2*n-1];i;i=e[i].next) if (i%2==0&&e[i].v==2*n) {e[i].f=inf;break;} for(int i=first[2*n];i;i=e[i].next) if (i%2==0&&e[i].v==t) {e[i].f=inf;break;} printf("%d",dinic()); return 0; }
试题库问题
[b]更新时间: 2018.1.21测试地址:试题库问题
做法:本题需要用到最大流。
本题也是经典的二分图多重匹配的模型,不过不像圆桌问题那样可以随便匹配了,每个类型只对应一部分题目。那么显然,对于所有类型ii,连接(S,i,pi)(S,i,pi),对于所有题目ii,连接(i,T,1)(i,T,1)(因为每道题目只能用一次),如果题目jj包含类型ii,那么连接(i,j,1)(i,j,1),对这个网络跑一遍最大流,然后照和圆桌问题类似的输出方案的方法输出即可。
以下是本人代码:
#include<bits/stdc++.h> #define s 0 #define t m+n+1 #define inf 1000000000 using namespace std; int m,n,sum=0,first[1510]={0},tot=1,lvl[1510]; struct edge {int v,next,f;} e[200010]; queue <int> Q; void insert(int a,int b,int f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot; } bool makelevel() { lvl[s]=0; for(int i=1;i<=t;i++) lvl[i]=-1; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; Q.push(e[i].v); } } return lvl[t]!=-1; } int maxflow(int v,int maxf) { if (v==t) return maxf; int ret=0,f; for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(e[i].f,maxf-ret)); e[i].f-=f; e[i^1].f+=f; ret+=f; if (ret==maxf) break; } return ret; } int dinic() { int maxf=0; while(makelevel()) maxf+=maxflow(0,inf); return maxf; } void output() { for(int i=1;i<=m;i++) { printf("%d:",i); for(int j=first[i];j;j=e[j].next) if (!e[j].f&&j%2==0&&e[j].v>m) printf(" %d",e[j].v-m); printf("\n"); } } int main() { scanf("%d%d",&m,&n); for(int i=1;i<=m;i++) { int p; scanf("%d",&p); insert(s,i,p); sum+=p; } for(int i=1;i<=n;i++) { int p,a; scanf("%d",&p); while(p--) { scanf("%d",&a); insert(a,m+i,1); } insert(m+i,t,1); } if (dinic()!=sum) printf("No Solution!"); else output(); return 0; }
星际转移问题
[b]更新时间: 2018.1.24测试地址:星际转移问题
做法:本题需要用到最大流。
由于最终的天数不确定,所以我们枚举答案,转化为判定性问题:ansans天内人能不能全部到达月球?对于每一天,建n+2n+2个点,表示这一天的地球、nn个太空站和月球,然后建模就比较容易了:
对于每一天的地球,连接(S,earthi,inf)(S,earthi,inf);
对于每一天的月球,连接(mooni,T,inf)(mooni,T,inf);
对于所有飞船,如果前一天在点xx,当天在点yy,那么连接(xi−1,yi,h)(xi−1,yi,h);
对于每个点xx,连接(xi−1,xi,inf)(xi−1,xi,inf)(人留在原地等的情况)。
这样一来,只要当天的最大流达到了kk,就说明当天可以有kk个人到达月球了。因为我们是从小到大枚举答案,所以我们可以在每一次做完的残余网络上再加新边再跑,这样会跑的快些(大概吧)。
最后还有一个问题,判断无解的问题。网上有人直接估算可能达到的最大天数,这样有WA或TLE的风险,最稳的方法还是并查集判断连通性,详见代码。
以下是本人代码:
#include<bits/stdc++.h> #define inf 1000000000 #define s 0 #define t 10009 using namespace std; int n,m,k,first[20010]={0},tot=1,lvl[20010]={0},saved=0,tim=1; int h[25],r[25],p[25][25],fa[25]; struct edge {int v,next,f;} e[1000010]; queue <int> Q; void insert(int a,int b,int f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot; } bool makelevel() { lvl[s]=0; for(int i=1;i<=(tim+1)*(n+2);i++) lvl[i]=-1; lvl[t]=-1; Q.push(0); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; Q.push(e[i].v); } } return lvl[t]!=-1; } int maxflow(int v,int maxf) { if (v==t) return maxf; int ret=0,f; for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(e[i].f,maxf-ret)); e[i].f-=f; e[i^1].f+=f; ret+=f; if (ret==maxf) break; } return ret; } int dinic() { int maxf=saved; while(makelevel()) maxf+=maxflow(0,inf); saved=maxf; return maxf; } bool check() { for(int i=0;i<=n+1;i++) insert((tim-1)*(n+2)+i+1,tim*(n+2)+i+1,inf); insert(s,(tim-1)*(n+2)+1,inf); insert(tim*(n+2)+n+2,t,inf); for(int i=1;i<=m;i++) { int x=p[i][tim%r[i]],y=p[i][(tim-1+r[i])%r[i]]; insert((tim-1)*(n+2)+y+1,tim*(n+2)+x+1,h[i]); } dinic(); return saved>=k; } int ufs_find(int x) { int r=x,i=x,j; while(fa[r]!=r) r=fa[r]; while(i!=r) j=fa[i],fa[i]=r,i=j; return r; } void ufs_merge(int x,int y) { int fx=ufs_find(x),fy=ufs_find(y); if (fx!=fy) fa[fx]=fy; } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=0;i<=n+1;i++) fa[i]=i; for(int i=1;i<=m;i++) { scanf("%d%d",&h[i],&r[i]); for(int j=0;j<r[i];j++) { scanf("%d",&p[i][j]); if (p[i][j]==-1) p[i][j]=n+1; if (j>0) ufs_merge(p[i][j],p[i][j-1]); } } if (ufs_find(0)!=ufs_find(n+1)) {printf("0");return 0;} while(!check()) tim++; printf("%d",tim); return 0; }
最小割问题
太空飞行计划问题
[b]更新时间: 2018.1.20测试地址:太空飞行计划问题
做法:本题需要用到最小割。
本题是一个经典的求最大权闭合子图的模型。所谓闭合子图,是指求一个顶点集,使得该顶点集中任意一个点的出边都指向该顶点集中的点,而最大权闭合子图就是在所有闭合子图中求一个点权和最大的。因此,最大权闭合子图的模型常用来解决一些有依赖关系的选取问题,构图方法是:如果选aa就必须选bb,那么连接aa->bb。经过一些证明(具体证明看这里),可以得到一个用最小割的思想求解该问题的模型:
对于原图中所有的边uu->vv,连接(u,v,inf)(u,v,inf),接下来对于所有正权点uu,连接(S,u,valueu)(S,u,valueu),对于所有负权点vv,连接(v,T,|valuev|)(v,T,|valuev|)。然后对于这个网络求最小割(也就等同于求最大流),记作ff,在最小割分成的两个顶点集中,SS所在的集合为所求,而最大权和就是所有正权点权值和减去ff。
至于输出方案,求完最大流之后,从SS通过残余网络能到达的所有点就是所求的答案,虽然我不太会证明割边一定满流,有待学习。
以下是本人代码:
#include <bits/stdc++.h> #define ll long long using namespace std; ll inf=1000000000,sum=0; int m,n,first[110]={0},tot=1,lvl[110]={0}; struct edge {int v,next;ll f;} e[100010]; queue <int> Q; int read(bool &flag) { char c; int s=0; c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') s=s*10+c-'0',c=getchar(); flag=(c=='\n'||c=='\r'); return s; } void insert(int a,int b,ll f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot; } bool makelevel() { lvl[0]=0; for(int i=1;i<=m+n+1;i++) lvl[i]=-1; Q.push(0); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; Q.push(e[i].v); } } return lvl[m+n+1]!=-1; } ll maxflow(int v,ll maxf) { if (v==m+n+1) return maxf; ll ret=0,f; for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(e[i].f,maxf-ret)); e[i].f-=f; e[i^1].f+=f; ret+=f; if (ret==maxf) break; } return ret; } ll dinic() { ll maxf=0; while(makelevel()) maxf+=maxflow(0,inf); return maxf; } int main() { inf*=inf; scanf("%d%d",&m,&n); for(int i=1;i<=m;i++) { ll f; bool flag=0; scanf("%lld",&f); sum+=f; insert(0,i,f); while(true) { int s=read(flag); insert(i,m+s,inf); if (flag) break; } } for(int i=1;i<=n;i++) { ll f; scanf("%lld",&f); insert(m+i,m+n+1,f); } ll ans=sum-dinic(); for(int i=1;i<=m;i++) if (lvl[i]!=-1) printf("%d ",i); printf("\n"); for(int i=1;i<=n;i++) if (lvl[i+m]!=-1) printf("%d ",i); printf("\n%lld",ans); return 0; }
方格取数问题
[b]更新时间: 2018.1.21测试地址:方格取数问题
做法:本题需要用到最小割。
将每个格子看做点,然后将每个点和上下左右四个点连边,显然地,因为该图不存在奇环,所以网格图是一个二分图。那么问题就转化为求二分图的最大点权和独立集,这时我们想办法将其转化为我们更加熟悉的模型解决。
我们试探性地往图中加入源点和汇点,然后对于二分图的其中一部分,从SS到所有该部分中的点连边,从所有另一部分中的点到TT连边,边权为该点对应的格子上数的值,接下来将原二分图中所有的边权设为infinf,方向设为从连接SS的点指向连接TT的点,我们来看看这个图有什么性质。可以看出,每一条从SS到TT的路径都对应了一对不能同时取的点,所以这些路径都不能存在,因此我们需要去掉一些边使得从SS不能到达TT,这里我们只会去掉边权不为infinf的边,因此每去掉一条边都相当于放弃了与这条边相连的那个点。而我们又要使得剩下的点的点权和最大,也就意味着剩下的边权不为infinf的边权和最大,也就是删掉的边的边权和最小,这显然就是一个最小割了。所以我们按照上述方法建一个网络,容量即为边权,然后跑一遍最大流,因为最大流等于最小割,所以用所有数的和减去得到的结果就是最后的答案。
以下是本人代码:
#include<bits/stdc++.h> #define s 0 #define t m*n+1 #define inf 1000000000 using namespace std; int m,n,sum=0,first[10010]={0},tot=1,lvl[10010]; struct edge {int v,next,f;} e[200010]; queue <int> Q; void insert(int a,int b,int f) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot; } bool makelevel() { lvl[s]=0; for(int i=1;i<=t;i++) lvl[i]=-1; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==-1) { lvl[e[i].v]=lvl[v]+1; Q.push(e[i].v); } } return lvl[t]!=-1; } int maxflow(int v,int maxf) { if (v==t) return maxf; int ret=0,f; for(int i=first[v];i;i=e[i].next) if (e[i].f&&lvl[e[i].v]==lvl[v]+1) { f=maxflow(e[i].v,min(e[i].f,maxf-ret)); e[i].f-=f; e[i^1].f+=f; ret+=f; if (ret==maxf) break; } return ret; } int dinic() { int maxf=0; while(makelevel()) maxf+=maxflow(0,inf); return maxf; } int main() { scanf("%d%d",&m,&n); for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) { int a; scanf("%d",&a); sum+=a; if ((i+j)%2==0) { if (i>1) insert((i-1)*n+j,(i-2)*n+j,inf); if (i<m) insert((i-1)*n+j,i*n+j,inf); if (j>1) insert((i-1)*n+j,(i-1)*n+j-1,inf); if (j<n) insert((i-1)*n+j,(i-1)*n+j+1,inf); insert(s,(i-1)*n+j,a); } else insert((i-1)*n+j,t,a); } printf("%d",sum-dinic()); return 0; }
费用流问题
餐巾计划问题
[b]更新时间: 2018.1.19测试地址:餐巾计划问题
做法:本题需要用到费用流。
将本题的模型简化一下,可以知道第ii天的入度来源于:新购买的餐巾,前一天没用完的干净餐巾,刚快洗或慢洗完的餐巾,而出度都指向第i+mi+m天(快洗)和第i+ni+n天(慢洗),而且要求点流量必须大于等于riri。那么显然我们把代表一天的点拆成两个点u,vu,v,并连接<u,v,ri,inf><u,v,ri,inf>。根据上下界网络流的解法(实际上是看大佬题解抄来的),这条边等价于(S,v,ri)+(u,T,ri)+(u,v,inf)(S,v,ri)+(u,T,ri)+(u,v,inf)。剩下的建模就很简单了,只需要连(S,u1,inf,p)(S,u1,inf,p)(相当于把所有要买的餐巾一上来先买好),(vi,ui+m,inf,f)(vi,ui+m,inf,f)(快洗)和(vi,ui+n,inf,s)(vi,ui+n,inf,s)(慢洗),然后跑个正常的费用流即可。
以下是本人代码:
#include <bits/stdc++.h> #define ll long long #define inf 1000000000 using namespace std; int N,first[5010]={0},tot=1,laste[5010],last[5010]; ll p,m,f,n,s,dis[5010]; bool vis[5010]={0}; struct edge {ll v,f,c,next;} e[200010]; queue <int> Q; void insert(int a,int b,ll f,ll c) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot; } bool spfa() { ll s; while(!Q.empty()) Q.pop(); Q.push(0); memset(vis,0,sizeof(vis)); dis[0]=0;vis[0]=1; for(int i=1;i<=2*N+1;i++) dis[i]=inf; while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&dis[v]+e[i].c<dis[e[i].v]) { dis[e[i].v]=dis[v]+e[i].c; laste[e[i].v]=i; last[e[i].v]=v; if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1; } vis[v]=0; } return dis[2*N+1]!=inf; } ll mincost() { ll ans=0; while(spfa()) { int x=2*N+1; ll minf=inf; while(x) minf=min(minf,e[laste[x]].f),x=last[x]; x=2*N+1; while(x) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=minf*dis[2*N+1]; } return ans; } int main() { scanf("%d",&N); for(int i=1;i<=N;i++) { ll r; scanf("%lld",&r); insert(0,2*i,r,0); insert(2*i-1,2*N+1,r,0); insert(2*i-1,2*i,inf,0); } scanf("%lld%lld%lld%lld%lld",&p,&m,&f,&n,&s); insert(0,1,inf,p); for(int i=1;i<=N;i++) { if (i<N) insert(2*i-1,2*i+1,inf,0); if (i+m<=N) insert(2*i,2*(i+m)-1,inf,f); if (i+n<=N) insert(2*i,2*(i+n)-1,inf,s); } printf("%lld",mincost()); return 0; }
航空路线问题
[b]更新时间: 2018.1.23测试地址:航空路线问题
做法:本题需要用到费用流。
首先,题目求的是一个环,包含从西向东和从东向西两条不相交(除起点和终点)的路径,显然可以看做两条从西向东的两条不相交(除起点和终点)路径来求。因为每个城市只能经过一次,所以有点流量限制,因此我们把一个点拆成两个点ui,viui,vi,并在中间连接(ui,vi,maxf,1)(ui,vi,maxf,1),显然对于除起点和终点的其他城市,点流量限制为11,否则为22。其他部分就比较简单了,先连接(S,u1,2)(S,u1,2)和(vn,T,inf)(vn,T,inf),再将原图的无向边改成从西向东的有向边ii->jj,因为我们拆了点,所以实际连的是(vi,uj,inf)(vi,uj,inf)。再然后,因为要求经过的点最多,所以跑最大费用最大流即可,只需将SPFA中求最短路改成求最长路即可。特殊地,如果最大流小于22则无解。至于输出方案,应该不难,但是要注意细节。
以下是本人代码:
#include<bits/stdc++.h> #define s 0 #define t 2*n+1 #define inf 1000000000 using namespace std; int n,m,first[210]={0},tot=1,dis[210],last[210],laste[210],maxf,path[210]; char name[110][20],names[20]; struct edge {int v,next,f,c;} e[100010]; bool vis[210]={0}; queue <int> Q; void insert(int a,int b,int f,int c) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot; } bool spfa() { dis[s]=0,vis[s]=1; for(int i=1;i<=t;i++) dis[i]=-inf; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c) { dis[e[i].v]=dis[v]+e[i].c; last[e[i].v]=v; laste[e[i].v]=i; if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v); } vis[v]=0; } return dis[t]!=-inf; } int maxcost() { int ans=0; maxf=0; while(spfa()) { int x=t,minf=inf; while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x]; x=t; while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=dis[t]*minf; maxf+=minf; } return ans; } void output() { int x,i; bool flag=0; printf("%s\n",name[1]); for(i=first[2];i;i=e[i].next) if ((e[i].f==inf-1||e[i].f==inf-2)&&i%2==0) break; x=e[i].v; while(x!=2*n-1) { printf("%s\n",name[(x+1)/2]); x++; for(int j=first[x];j;j=e[j].next) if (e[j].f==inf-1&&j%2==0) {x=e[j].v;break;} } printf("%s\n",name ); if (e[i].f!=inf-2) { i=e[i].next; for(;i;i=e[i].next) if (e[i].f==inf-1&&i%2==0) break; x=e[i].v; path[0]=0; while(x!=2*n-1) { path[++path[0]]=(x+1)/2; x++; for(int j=first[x];j;j=e[j].next) if (e[j].f==inf-1&&j%2==0) {x=e[j].v;break;} } for(i=path[0];i>=1;i--) printf("%s\n",name[path[i]]); } printf("%s",name[1]); } int find() { for(int i=1;i<=n;i++) { bool flag=1; int len=max(strlen(name[i]),strlen(names)); for(int j=0;j<len;j++) if (name[i][j]!=names[j]) {flag=0;break;} if (flag) return i; } } int main() { scanf("%d%d",&n,&m); insert(s,1,2,0); insert(2*n,t,inf,0); for(int i=1;i<=n;i++) { if (i==1||i==n) insert(2*i-1,2*i,2,1); else insert(2*i-1,2*i,1,1); scanf("%s",name[i]); } for(int i=1;i<=m;i++) { int a,b; scanf("%s",names); a=find(); scanf("%s",names); b=find(); if (a>b) swap(a,b); insert(2*a,2*b-1,inf,0); } int ans=maxcost()-2; if (maxf<2) printf("No Solution!"); else { printf("%d\n",ans); output(); } return 0; }
软件补丁问题
[b]更新时间: 2018.1.23测试地址:软件补丁问题
做法:本题需要用到费用流。
不难想到,将错误的2n2n个状态全部存储成点,然后把“使用补丁”这一过程看作状态的转移,用位运算把图建出来,最后从(111...1)(111...1)到(000...0)(000...0)的最短路径就是答案。没错,一次最短路就能解决的事情为什么要放在网络流24题里呢……虽然这样,但为了达到练习(方便Ctrl+C和Ctrl+V)的效果,我的代码还是用费用流的模板写的。
然而,要注意的是,如果我们在做之前就把图建好,结果是无论如何都会MLE或者RE,这时我们就要动态加边,就是等到用到时才把边建出来,我也不知道为什么加了这个之后就能过了,大概是玄学吧……
以下是本人代码:
#include <bits/stdc++.h> #define s 0 #define t (1<<n)+1 #define ll long long #define inf 1000000000 using namespace std; int n,m,first[2000010]={0},tot=1,laste[2000010],last[2000010]; int b1[110]={0},b2[110]={0},f1[110]={0},f2[110]={0}; ll dis[2000010],tim[110]; bool vis[2000010]={0},visit[2000010]={0}; struct edge {int v,next;ll f,c;} e[2000010]; queue <int> Q; void insert(int a,int b,ll f,ll c) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot; } bool spfa() { Q.push(s); dis[s]=0;vis[s]=1; for(int i=1;i<=t;i++) dis[i]=inf; while(!Q.empty()) { int v=Q.front();Q.pop(); if (v&&!visit[v]) { v--; for(int i=1;i<=m;i++) if ((v&b1[i])==b1[i]&&(v&b2[i])==0) insert(v+1,((v-(v&f1[i]))|f2[i])+1,1,tim[i]); v++; visit[v]=1; } for(int i=first[v];i;i=e[i].next) if (e[i].f&&dis[v]+e[i].c<dis[e[i].v]) { dis[e[i].v]=dis[v]+e[i].c; laste[e[i].v]=i; last[e[i].v]=v; if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1; } vis[v]=0; } return dis[t]!=inf; } ll mincost() { ll ans=0; while(spfa()) { int x=t; ll minf=inf; while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x]; x=t; while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=minf*dis[t]; } return ans; } int main() { scanf("%d%d",&n,&m); insert(s,1<<n,1,0); insert(1,t,1,0); for(int i=1;i<=m;i++) { char b[20],f[20]; scanf("%lld%s%s",&tim[i],b,f); for(int j=0;j<n;j++) { if (b[j]=='+') b1[i]+=(1<<j); if (b[j]=='-') b2[i]+=(1<<j); if (f[j]=='-') f1[i]+=(1<<j); if (f[j]=='+') f2[i]+=(1<<j); } } printf("%lld",mincost()); return 0; }
数字梯形问题
[b]更新时间: 2018.1.25测试地址:数字梯形问题
做法:本题需要用到费用流。
对于这道题,因为每次只能往左下和右下两个数字走,所以边相交的情况实际上只有重边的情况,所以直接考虑网络流建模。
对于第一问,相当于限制了点容量和往下走的边容量,因此惯例把每个点拆成两个点即可,剩下的建模就直接按照转移图建就行。
对于第二问,相当于只限制了往下走的边容量,这时要注意,从最下面一行到汇点的边不属于限制边,因为有可能出现路径在最后一行相交的情况。这时我们只需把点容量限制取消即可。
对于第三问,相当于没有限制,取消所有限制即可。
按照上述方式建完图后,在点容量的边上加一个费用,即这个点上数的值,然后跑最大费用最大流即可。
以下是本人代码:
#include<bits/stdc++.h> #define ll long long #define s 0 #define t 509 #define inf 1000000000 using namespace std; int m,n,p=0,first[1010]={0},tot=1; int last[1010],laste[1010]; ll dis[1010]; struct edge {int v,next,type;ll f,c;} e[200010]; queue <int> Q; bool vis[1010]={0}; void insert(int a,int b,ll f,ll c,int type) { e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].type=type,e[tot].next=first[a],first[a]=tot; e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].type=type,e[tot].next=first,first[b]=tot; } bool spfa() { dis[s]=0; for(int i=1;i<=2*p;i++) dis[i]=-inf; dis[t]=-inf; vis[s]=1; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c) { laste[e[i].v]=i; last[e[i].v]=v; dis[e[i].v]=dis[v]+e[i].c; if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1; } vis[v]=0; } return dis[t]>0; } ll maxcost() { ll ans=0,minf; while(spfa()) { minf=inf; int x=t; while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x]; x=t; while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=minf*dis[t]; } return ans; } int main() { scanf("%d%d",&m,&n); for(int i=1;i<=n;i++) for(int j=1;j<=m+i-1;j++) { ll a; scanf("%lld",&a); p++; if (i==1) insert(s,2*p-1,1,0,0); if (i==n) insert(2*p,t,inf,0,1); insert(2*p-1,2*p,1,a,1); if (i>1&&j>1) insert(2*(p-m-i+1),2*p-1,1,0,2); if (i>1&&j<m+i-1) insert(2*(p-m-i+2),2*p-1,1,0,2); } printf("%lld\n",maxcost()); for(int i=2;i<=tot;i+=2) { if (e[i].type==1) e[i].f=inf; else e[i].f=1; e[i^1].f=0; } printf("%lld\n",maxcost()); for(int i=2;i<=tot;i+=2) { if (e[i].type>0) e[i].f=inf; else e[i].f=1; e[i^1].f=0; } printf("%lld",maxcost()); return 0; }
运输问题
[b]更新时间: 2018.1.25测试地址:运输问题
做法:本题需要用到费用流。
做了那么多题的你看到这样的题,应该很快就能想出建模方法了:
对于所有仓库ii,连接(S,i,ai,0)(S,i,ai,0);
对于所有商店jj,连接(j,T,bj,0)(j,T,bj,0);
对于所有运输方法ii->jj,连接(i,j,inf,cij)(i,j,inf,cij)。
对上面的网络,分别跑一次最小费用最大流和最大费用最大流即可。
以下是本人代码:
#include<bits/stdc++.h> #define ll long long #define s 0 #define t m+n+1 using namespace std; int m,n,first[210]={0},tot=1; int last[210],laste[210]; ll dis[210],inf; struct edge {int v,next;ll f,c;} e[200010]; queue <int> Q; bool vis[210]={0}; void insert(int a,int b,ll f,ll c) { e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].next=first[a],first[a]=tot; e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].next=first,first[b]=tot; } bool spfa(bool type) { dis[s]=0; for(int i=1;i<=t;i++) dis[i]=type?inf:-inf; vis[s]=1; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&((!type&&dis[e[i].v]<dis[v]+e[i].c)||(type&&dis[e[i].v]>dis[v]+e[i].c))) { laste[e[i].v]=i; last[e[i].v]=v; dis[e[i].v]=dis[v]+e[i].c; if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1; } vis[v]=0; } return dis[t]!=(type?inf:-inf); } ll mincost(bool type) { ll ans=0,minf; while(spfa(type)) { minf=inf; int x=t; while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x]; x=t; while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=minf*dis[t]; } return ans; } int main() { inf=1000000000; inf*=inf; scanf("%d%d",&m,&n); for(int i=1;i<=m;i++) { ll a; scanf("%lld",&a); insert(s,i,a,0); } for(int i=1;i<=n;i++) { ll a; scanf("%lld",&a); insert(m+i,t,a,0); } for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) { ll a; scanf("%lld",&a); insert(i,m+j,inf,a); } printf("%lld\n",mincost(1)); for(int i=2;i<=tot;i+=2) e[i].f=e[i].f+e[i^1].f,e[i^1].f=0; printf("%lld",mincost(0)); return 0; }
分配问题
[b]更新时间: 2018.1.25测试地址:分配问题
做法:本题需要用到费用流。
本题是经典的二分图最佳匹配模型,可以用KM算法做,当然也可以用费用流来解。建图的方法和上面那题基本一样,只不过从源点流出和进入汇点的边容量都是11,建完图后分别跑一次最小费用最大流和最大费用最大流即可。
以下是本人代码:
#include<bits/stdc++.h> #define ll long long #define s 0 #define t 2*n+1 using namespace std; int n,first[210]={0},tot=1; int last[210],laste[210]; ll dis[210],inf; struct edge {int v,next;ll f,c;} e[200010]; queue <int> Q; bool vis[210]={0}; void insert(int a,int b,ll f,ll c) { e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].next=first[a],first[a]=tot; e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].next=first,first[b]=tot; } bool spfa(bool type) { dis[s]=0; for(int i=1;i<=t;i++) dis[i]=type?inf:-inf; vis[s]=1; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&((!type&&dis[e[i].v]<dis[v]+e[i].c)||(type&&dis[e[i].v]>dis[v]+e[i].c))) { laste[e[i].v]=i; last[e[i].v]=v; dis[e[i].v]=dis[v]+e[i].c; if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1; } vis[v]=0; } return dis[t]!=(type?inf:-inf); } ll mincost(bool type) { ll ans=0,minf; while(spfa(type)) { minf=inf; int x=t; while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x]; x=t; while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=minf*dis[t]; } return ans; } int main() { inf=1000000000; inf*=inf; scanf("%d",&n); for(int i=1;i<=n;i++) { insert(s,i,1,0); insert(n+i,t,1,0); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { ll a; scanf("%lld",&a); insert(i,n+j,inf,a); } printf("%lld\n",mincost(1)); for(int i=2;i<=tot;i+=2) e[i].f=e[i].f+e[i^1].f,e[i^1].f=0; printf("%lld",mincost(0)); return 0; }
负载平衡问题
[b]更新时间: 2018.1.25测试地址:负载平衡问题
做法:本题需要用到费用流。
虽然这题貌似可以用贪心或者中位数啥啥的做法水过,但是这题的数据范围明显适用网络流,而且我们很快就能想出建模的方法。
对于每个仓库,建立一个供给节点和一个需求节点,对于这道题,每个仓库的供给量就是一开始的货物数,需求量就是总货物数/仓库数,而一份货物从一个仓库转移到另一个仓库需要一些代价。看到这里,其实建模思路已经非常明显了,按照运输问题那样的建模方式建,然后跑最小费用最大流即可。
以下是本人代码:
#include<bits/stdc++.h> #define ll long long #define s 0 #define t 2*n+1 #define inf 1000000000 using namespace std; int n,p=0,first[210]={0},tot=1; int last[210],laste[210]; ll dis[210],sum=0; struct edge {int v,next;ll f,c;} e[200010]; queue <int> Q; bool vis[210]={0}; void insert(int a,int b,ll f,ll c) { e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].next=first[a],first[a]=tot; e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].next=first,first[b]=tot; } bool spfa() { dis[s]=0; for(int i=1;i<=t;i++) dis[i]=inf; vis[s]=1; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&dis[e[i].v]>dis[v]+e[i].c) { laste[e[i].v]=i; last[e[i].v]=v; dis[e[i].v]=dis[v]+e[i].c; if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1; } vis[v]=0; } return dis[t]!=inf; } ll mincost() { ll ans=0,minf; while(spfa()) { minf=inf; int x=t; while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x]; x=t; while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=minf*dis[t]; } return ans; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { ll a; scanf("%lld",&a); sum+=a; insert(s,i,a,0); } for(int i=1;i<=n;i++) insert(n+i,t,sum/n,0); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) insert(i,n+j,inf,min(abs(i-j),n-abs(i-j))); printf("%lld",mincost()); return 0; }
深海机器人问题
[b]更新时间: 2018.1.26测试地址:深海机器人问题
做法:本题需要用到费用流。
这题唯一比较难的点是如何处理每条边只能取一次,但其实也不难,只需要先连(u,v,1,x)(u,v,1,x),然后再连一个(u,v,inf,0)(u,v,inf,0)即可,其他建图应该比较容易了,建完之后跑最大费用最大流即可。
以下是本人代码:
#include<bits/stdc++.h> #define s 0 #define t (p+1)*(q+1)+1 #define ll long long using namespace std; int p,q,a,b,first[510]={0},tot=1,last[510],laste[510]; ll dis[510],inf; struct edge {int v,next;ll f,c;} e[200010]; bool vis[510]={0}; queue <int> Q; void insert(int a,int b,ll f,ll c) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot; } bool spfa() { dis[s]=0,vis[s]=1; for(int i=1;i<=t;i++) dis[i]=-inf; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c) { dis[e[i].v]=dis[v]+e[i].c; last[e[i].v]=v; laste[e[i].v]=i; if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v); } vis[v]=0; } return dis[t]!=-inf; } ll maxcost() { ll ans=0; while(spfa()) { int x=t; ll minf=inf; while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x]; x=t; while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=dis[t]*minf; } return ans; } int main() { inf=1000000000; inf*=inf; scanf("%d%d%d%d",&a,&b,&p,&q); for(int i=0;i<p+1;i++) for(int j=0;j<q;j++) { ll x; scanf("%lld",&x); insert(j*(p+1)+i+1,(j+1)*(p+1)+i+1,1,x); insert(j*(p+1)+i+1,(j+1)*(p+1)+i+1,inf,0); } for(int j=0;j<q+1;j++) for(int i=0;i<p;i++) { ll x; scanf("%lld",&x); insert(j*(p+1)+i+1,j*(p+1)+i+2,1,x); insert(j*(p+1)+i+1,j*(p+1)+i+2,inf,0); } for(int i=1;i<=a;i++) { ll k;int x,y; scanf("%lld%d%d",&k,&x,&y); swap(x,y); insert(s,x*(p+1)+y+1,k,0); } for(int i=1;i<=b;i++) { ll k;int x,y; scanf("%lld%d%d",&k,&x,&y); swap(x,y); insert(x*(p+1)+y+1,t,k,0); } printf("%lld",maxcost()); return 0; }
最长k可重区间集问题
[b]更新时间: 2018.1.26测试地址:最长k可重区间集问题
做法:本题需要用到费用流。
尝试建一个有向图:将一个区间看做一个点,点权为该区间的长度,若两个区间i,ji,j不相交,且ii在jj之前,则连接ii->jj。不难看出,问题可以转化为在这个有向图中选取kk条不相交的路径,使得路径上的点权总和最大。因为每个点都有可能是起点或者终点,所以从SS向所有点、从所有点向TT连边,而又因为题目只限制了总流量,所以应该将SS拆成两个点,然后连接(S1,S2,k)(S1,S2,k)。题目要求路径不相交,那么每个点肯定只能经过一次,因此将每个点拆成两个点,然后连接(xi,yi,1,valuei)(xi,yi,1,valuei)。其他的边就按照原有向图的边连即可,容量为infinf,费用为00,建完图后跑一遍最大费用最大流即可。
(据说还有另一种建模方式,有待学习)
以下是本人代码:
#include<bits/stdc++.h> #define s 0 #define ss 2*n+2 #define t 2*n+1 #define ll long long using namespace std; int n,k,first[1010]={0},tot=1,last[1010],laste[1010]; ll l[1010],r[1010],dis[1010],inf; struct edge {int v,next;ll f,c;} e[500010]; bool vis[1010]={0}; queue <int> Q; void insert(int a,int b,ll f,ll c) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot; } bool spfa() { dis[s]=0,vis[s]=1; for(int i=1;i<=ss;i++) dis[i]=-inf; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c) { dis[e[i].v]=dis[v]+e[i].c; last[e[i].v]=v; laste[e[i].v]=i; if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v); } vis[v]=0; } return dis[t]!=-inf; } ll maxcost() { ll ans=0; while(spfa()) { int x=t; ll minf=inf; while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x]; x=t; while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=dis[t]*minf; } return ans; } int main() { inf=1000000000; inf*=inf; scanf("%d%d",&n,&k); insert(s,ss,k,0); for(int i=1;i<=n;i++) { scanf("%lld%lld",&l[i],&r[i]); insert(ss,2*i-1,inf,0); insert(2*i,t,inf,0); insert(2*i-1,2*i,1,r[i]-l[i]); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if (l[j]>=r[i]) insert(2*i,2*j-1,inf,0); printf("%lld",maxcost()); return 0; }
最长k可重线段集问题
[b]更新时间: 2018.1.27测试地址:最长k可重线段集问题
做法:本题需要用到费用流。
本题的做法和上面的最长k可重区间集差不多,区别有:
1.线段长度的计算不同了(…废话),注意在洛谷上必须要开long long才不会爆。
2.不同于区间,开区间的左右端点不可能是一个数,但是开线段的两个端点的横坐标可能相同,也就是垂直于xx轴,解决方法是:将所有横坐标乘以2,如果一条线段两端点的横坐标相同,那么右端点++,否则左端点++,这样就可以将左右端点隔开了。
其他建模方法和上一题相同,详见代码。
以下是本人代码:
#include<bits/stdc++.h> #define s 0 #define ss 2*n+2 #define t 2*n+1 #define ll long long using namespace std; int n,k,first[1010]={0},tot=1,last[1010],laste[1010]; ll xz[1010],yz[1010],xo[1010],yo[1010],dis[1010],inf; struct edge {int v,next;ll f,c;} e[500010]; bool vis[1010]={0}; queue <int> Q; void insert(int a,int b,ll f,ll c) { e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot; e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot; } bool spfa() { dis[s]=0,vis[s]=1; for(int i=1;i<=ss;i++) dis[i]=-inf; Q.push(s); while(!Q.empty()) { int v=Q.front();Q.pop(); for(int i=first[v];i;i=e[i].next) if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c) { dis[e[i].v]=dis[v]+e[i].c; last[e[i].v]=v; laste[e[i].v]=i; if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v); } vis[v]=0; } return dis[t]!=-inf; } ll maxcost() { ll ans=0; while(spfa()) { int x=t; ll minf=inf; while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x]; x=t; while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x]; ans+=dis[t]*minf; } return ans; } int main() { inf=1000000000; inf*=inf; scanf("%d%d",&n,&k); insert(s,ss,k,0); for(int i=1;i<=n;i++) { scanf("%lld%lld%lld%lld",&xz[i],&yz[i],&xo[i],&yo[i]); if (xo[i]<xz[i]) swap(xz[i],xo[i]),swap(yz[i],yo[i]); insert(ss,2*i-1,inf,0); insert(2*i,t,inf,0); insert(2*i-1,2*i,1,(ll)sqrt((xo[i]-xz[i])*(xo[i]-xz[i])+(yo[i]-yz[i])*(yo[i]-yz[i]))); xz[i]<<=1,xo[i]<<=1; if (xz[i]==xo[i]) xo[i]++; else xz[i]++; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if (xz[j]>=xo[i]) insert(2*i,2*j-1,inf,0); printf("%lld",maxcost()); return 0; }
不可做问题
机器人路径规划问题
[b]更新时间: 2018.1.21测试地址:机器人路径规划问题
做法:本题是这24题中唯一一道不能用网络流算法解决的问题,需要用一堆诡异的随机化算法解决,但据说有大牛弄出来了O(n6)O(n6)的DP做法,这里只附个链接供大家膜拜:
大佬 Orz <-我
相关文章推荐
- 网络流24题解题报告小结
- 网络流24题做题记录(更新中)
- 【专题总结】网络流与二分图(持续更新)
- leetcode解题总结(持续更新)
- 线性规划与网络流24题解题报告
- 蒟蒻的网络流24题解题记
- 网络流总结(持续更新中)
- 自写网络流24题总结
- 网络流典题总结——24题
- 【多题合集】网络流24题练习(更新至魔术球问题)
- leecode 解题总结:331. Verify Preorder Serialization of a Binary Tree
- SharePoint 2010 自定义Timer job 问题总结(拒绝访问,Execute方法不执行,不及时更新)
- 线性规划以网络流24题の10 餐巾计划问题(费用流)
- 移动端总结(更新)
- git 命令总结 (持续更新)
- 培训总结集_(不更新)
- 学习注意点总结:持续更新~
- maven更新总结与tomcat发布方法总结
- 【网络流24题20】深海机器人问题
- leetcode第四周解题总结(9,21,2,24)