网络流问题
网络流
网络流问题常见的求解目标有最大流(最小割)、最小费用最大流、上下界可行流等
最小割
最大流还有一个很重要的应用,就是求最小割,以下是一些定理,其实这些和二分图匹配里面的有点相似:
最小割 = 最大流
最大点权覆盖集 = 最小割
最小点权独立集 = 总权值 - 最大点权覆盖集
最小割的定义:把网络划分成两个集合,s,t使得在s集合和在t集合中的任意两点互相不相连,去掉的边就是割边,而这些割边代价总和就是割,最小割就是代价最小的划分方法
一类最小割的题目还是挺明显,如果是要把存在点划分两个集合,求最小代价之类的,就很明显是最小割了
最小割有一些挺经典的模型:
1、平面图求最小割:
这个做法就是,把平面每一部分面积当成点,然后相邻块连边,然后增设源点s和汇点t,分别在原来的入口出口的另一对角线上,和相应两部分边连边,这时候,每一个最小割,其实就是一个s到t的路径,那么求最短路就是最小割了
2、最小权闭合
这个做法是源点s连向正权值,负权值连向汇点t,之间关系连边容量INF,求出最小割之后,这个最小割就是最少损失,然后总权值 - 最小割得到的就是最大权闭合图的权值
关于最小割输出路径
根据题意,如果要s集合尽量多,就从t集合dfs,反之则从s集合dfs,不经过没有容量的边即可
费用流
一类K覆盖问题:
这类问题一般表现为,一个区间或者一些点,每个可以最多被覆盖k次,然后有一些特殊的边可以走,但是只能走一次,这时候要求k覆盖后的最大权值
其实就是费用流,建边把特殊边建起来,对应相应费用,然后其他边直接相连,费用为0,注意由于是要求最大代价,而算法是求最小费用,其实和KM匹配求最小一样的思路,把边权弄成负数,跑一下即可
网络流的一些特殊问题
上下界网络流:
无源无汇有上下界最大流:
这个要根据流量平衡来搞,建图先把边容量定成上限up - 下限down,然后每一个点,记录下流入流量和流出流量,然后设一个超级源s,超级汇t,s连接流量正的点,流量负的点连向t,然后跑最大流,跑完之后如果从s流出的流量都是满流,就是有解,每个边的真实流量就为当前边流量,加上原来的下限
有源有汇有上下界最大流:
建图方法一致,不过要多连上一条t->s容量为INF的边,这样跑一下最大流,t->s的流量就是答案
有源有汇有上下界最小流:
也是一样,不过t->s先不连,先求一次最大流,然后在连t->s,在做一次最大流把残余流量充分利用,然后t->s的流量就是答案
分层网络流:
这类题,以时间为单位,这样对于每个时间点就要建一层结点,时间上限不是很大的话,就可以每多一个时间点,就多一层结点,在原来的图上继续增广
混合图找欧拉回路:
欧拉回路入度等于出度,然后无向图先任意定向,然后记录每个点的度数和,度数和 / 2就是需要调整的边数,然后把源点连向正的,负的连向汇点,然后中间的边就是连无向边,因为只有无向边可以调整,然后跑一下最大流即可
增加哪些边会使得最大流增加:
这类问题其实就是对于满流的边,如果左边源点到他和他到汇点,能有一个残量网络,这条边就是可以增加的,利用两个dfs,分别从源点汇点出发即可
最大密度子图:
先要记录下每个结点的度数
利用二分搜索来搜索答案g,然后根据这个建图判断,判断的方式为:
源点与原图中每一个点连一条容量为m的边。原图中每一个点与汇点连一条容量为m+2*g-度数的边,再将原图中的无向边拆成两条有向边,容量都设为1.然后对此图求最大流,最后将(n*m-maxflow)/2 与0比较大小,如果它大于0,l = g,
否则r = g
POJ 3281 Dining
题意:
一个人有喜欢的饮料跟食物,现在给出这些关系,问最多能有多少人得到喜欢的饮料跟食物
思路:
网络流的题目往往难在建模,我们很容易想到的一种建模方法就是源点-食物-人-饮料-汇点
但是这样的话可能一个人可以满足多次,所以为了加上人的这个限制条件,就需要将人拆点,并连上一条权值为1的边
拆点也是对于点限制的很好的解决方法
#include<iostream> #include<algorithm> #include<cstring> #include<queue> using namespace std; const int inf=1<<30; struct Graph{ static const int M=100010,N=10010; int dis[N],cur[N],head[N],cnt; void Grap(){ cnt=-1; memset(head,-1,sizeof head); } struct edge{ int to,val,next; }e[M<<1]; void _add(int u,int v,int val){ cnt++; e[cnt].next=head[u]; e[cnt].to=v; e[cnt].val=val; head[u]=cnt; } void add(int u,int v,int val){ _add(u,v,val); _add(v,u,0); } bool bfs(int s,int t){ queue<int> q; memset(dis,-1,sizeof dis); dis[s]=0; q.push(s); while(!q.empty()){ int u=q.front(); q.pop(); for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].to; if(dis[v]==-1&&e[i].val>0){ dis[v]=dis[u]+1; q.push(v); } } } return dis[t]!=-1; } int dfs(int s,int t,int maxflow){ if(s==t)return maxflow; int res=0; for(int& i=cur[s];i!=-1;i=e[i].next){ int v=e[i].to; if(dis[v]!=dis[s]+1||e[i].val<=0||res>=maxflow)continue; int f=dfs(v,t,min(e[i].val,maxflow-res)); e[i].val-=f; e[i^1].val+=f; res+=f; } return res; } int Dinic(int s,int t){ int ans=0; while(bfs(s,t)){ memcpy(cur,head,sizeof head); ans+=dfs(s,t,inf); } return ans; } }mode; int main() { mode.Grap(); int n,a,b; scanf("%d%d%d",&n,&a,&b); for(int i=1;i<=n;i++){ int f,d,x; scanf("%d%d",&f,&d); for(int j=1;j<=f;j++){ scanf("%d",&x); mode.add(x,a+i,1); } for(int j=1;j<=d;j++){ scanf("%d",&x); mode.add(a+n+i,2*n+a+x,1); } } for(int i=1;i<=n;i++) mode.add(a+i,a+n+i,1); for(int i=1;i<=a;i++) mode.add(0,i,1); for(int i=1;i<=b;i++) mode.add(2*n+a+i,2*n+a+b+1,1); cout<<mode.Dinic(0,2*n+a+b+1)<<endl; return 0; }
POJ 1087 A Plug for UNIX
题意:
有n个不同型号的插座(每种型号只有1个),m个不同的用电器且有着对应的插座型号(不同用电器对应的插座型号可能相同),同时有k种插座替代关系,例如A型号插座可以替代B型号插座(但B不一定能替代
A)(插座型号和用电器均用字符串表示)。问最少有多少用电器没有插座用。
思路:
建立源点-插座-用电器-汇点的网络
对于转换插座的话,可以直接在插座之间连上新的边,容量为inf(因为使用次数不限)
最后跑一个最大流
反思:在建图上出了很多的错误,建图连边的话不一定要很紧凑,这样建边不容易出错
#include<iostream> #include<algorithm> #include<cstring> #include<queue> #include<map> #include<set> #include<string> using namespace std; const int inf=1<<30; int n,m,k; const int maxn=1e4+10; string t1,t2; map<string,int> m1; int cnt=0; struct Graph{ static const int M=100010,N=10010; int dis[N],cur[N],head[N],cnt; void Grap(){ cnt=-1; memset(head,-1,sizeof head); } struct edge{ int to,val,next; }e[M<<1]; void _add(int u,int v,int val){ cnt++; e[cnt].next=head[u]; e[cnt].to=v; e[cnt].val=val; head[u]=cnt; } void add(int u,int v,int val){ _add(u,v,val); _add(v,u,0); } bool bfs(int s,int t){ queue<int> q; memset(dis,-1,sizeof dis); dis[s]=0; q.push(s); while(!q.empty()){ int u=q.front(); q.pop(); for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].to; if(dis[v]==-1&&e[i].val>0){ dis[v]=dis[u]+1; q.push(v); } } } return dis[t]!=-1; } int dfs(int s,int t,int maxflow){ if(s==t)return maxflow; int res=0; for(int& i=cur[s];i!=-1;i=e[i].next){ int v=e[i].to; if(dis[v]!=dis[s]+1||e[i].val<=0||res>=maxflow)continue; int f=dfs(v,t,min(e[i].val,maxflow-res)); e[i].val-=f; e[i^1].val+=f; res+=f; } return res; } int Dinic(int s,int t){ int ans=0; while(bfs(s,t)){ memcpy(cur,head,sizeof head); ans+=dfs(s,t,inf); } return ans; } }mode; int get(string s) { if(m1[s]==0) m1[s]=++cnt; return m1[s]; } int main() { mode.Grap(); int s=0,t=500; cin>>n; for(int i=1;i<=n;i++){ cin>>t1; mode.add(s,get(t1),1); } cin>>m; for(int i=1;i<=m;i++){ cin>>t1>>t2; mode.add(get(t2),200+i,1); mode.add(200+i,t,1); } cin>>k; for(int i=1;i<=k;i++){ cin>>t1>>t2; mode.add(get(t2),get(t1),inf); } cout<<m-mode.Dinic(s,t)<<endl; return 0; }
POJ 2195 Going Home
题意:
有n个人,n座房子,每个人到不同的房子有不同的距离,现在给每个人分配一座房子求所有人到对应房子的最短距离
思路:
方法①很明显这是一个二分图,题目要求求的是一个最小权值完备匹配,所以可以直接将权值取负,利用KM算法求得最小值
#include<cstdio> #include<cstring> #include<algorithm> #include<map> #include<queue> #include<iostream> using namespace std; const int N=205,INF=0x3f3f3f3f; struct node{ int x,y; }g[N],f[N]; int x,y,cnt1,cnt2; char mp[N][N]; map<int,int> m1,m2; int n,m,t,u,v,w[N][N],la[N],a,lb[N],mat[N],d,upd[N]; bool va[N],vb[N]; bool dfs(int u) { va[u]=true; for(int v=1;v<=n;v++) { if(vb[v]) continue; if (la[u]+lb[v]-w[u][v]==0) { vb[v]=true; if (!mat[v]||dfs(mat[v])) { mat[v]=u; return true; } } else upd[v]=min(upd[v],la[u]+lb[v]-w[u][v]); } return false; } int KM() { memset(mat,0,sizeof(mat)); for (int i=1;i<=n;i++) { la[i]=-INF; lb[i]=0; for(int j=1;j<=n;j++) { la[i]=max(la[i],w[i][j]); } } for(int i=1;i<=n;i++) { while(1){ memset(va,false,sizeof(va)); memset(vb,false,sizeof(vb)); for(int j=1;j<=n;j++) upd[j]=INF; if(dfs(i)) break; d=INF; for(int j=1;j<=n;j++) if(!vb[j]) d=min(d,upd[j]); for (int j=1;j<=n;j++) { if(va[j]) la[j]-=d; if(vb[j]) lb[j]+=d; } } } int ans=0; for (int i=1;i<=n;i++) ans+=w[mat[i]][i]; return -ans; } int main() { while(scanf("%d%d",&x,&y)&&x||y){ memset(w,0,sizeof(w)); cnt1=cnt2=0; for(int i=0;i<x;i++){ scanf("%s",mp[i]); for(int j=0;j<y;j++){ if(mp[i][j]=='m') g[++cnt1].x=i,g[cnt1].y=j; if(mp[i][j]=='H') f[++cnt2].x=i,f[cnt2].y=j; } } for(int i=1;i<=cnt1;i++) for(int j=1;j<=cnt1;j++) w[i][j]=-(abs(g[i].x-f[j].x)+abs(g[i].y-f[j].y)); n=cnt1; cout<<KM()<<endl; } return 0; }