最大流
概念:
网络流是指在一张图中,从源点(sp)到汇点(cp)每一时间点最大的流量。
举例:
假如我们想让尽量多的水从点s到点t,问题是1s最多有多少L水到达了t(我们假设水管中充满了水)
如上图,边上的数字是水管每秒所能通过的水量。
模板题:https://www.luogu.org/problem/P3376
对于s->v1->t这条线路只能流过4,因为虽然s->v1可以流过5而v1->t只能流4.
而s->v1->v2->t这条线路只能流过1(其实也可上流3,下流2),因为v1只剩了1.
增广路:
找到一条从源点至汇点的通路,流过一定流量。
反向弧:
对于每条弧,设q为它剩余的流量。
并且需建一条反向弧,初始q为0,使得流动可以撤销。
注意:q为可修改量,当流过流量q1时,正向弧q-=q1,反向弧q+=q1
EK 时间复杂度O(n*m^2)
EK的思路十分简单,就是不断的去找增广路,在找到后对该条增广路上的所有边的q做修改。
实际实现过程中,我们利用bfs的方法来寻找增广路,用fl[u]表示增广路流到u的流量。
fl[sp]=+无穷,然后如果fl[cp]==0就说明没有增广路了。
然后对于每个点,可以记录下到达它的那条边,这样我们在确定修改增广路之后就可以直接从cp一直向前找到增广路。
#include<cstdio> #include<queue> #include<cstring> using namespace std; const int N=1e4+5,inf=1e9; struct E{ int v,q,n; }e[N*20]; int fir[N],s=1,n,m,sp,cp,fl[N],pre[N]; queue<int>dl; void add(int u,int v,int q){ e[++s].v=v; e[s].q=q; e[s].n=fir[u]; fir[u]=s; } bool bfs(){ memset(fl,0,sizeof(fl)); dl.push(sp);fl[sp]=inf; while(!dl.empty()){ int u=dl.front();dl.pop(); for(int i=fir[u];i;i=e[i].n) if(e[i].q&&!fl[e[i].v]){ fl[e[i].v]=min(e[i].q,fl[u]); pre[e[i].v]=i; dl.push(e[i].v); } } return fl[cp]; } int main(){ int u,v,q,ans=0; scanf("%d%d%d%d",&n,&m,&sp,&cp); for(int i=1;i<=m;++i){ scanf("%d%d%d",&u,&v,&q); add(u,v,q),add(v,u,0); } while(bfs()){ ans+=fl[cp]; for(int i=cp;i!=sp;i=e[pre[i]^1].v){ e[pre[i]].q-=fl[cp]; e[pre[i]^1].q+=fl[cp]; } } printf("%d",ans); return 0; }
Dinic 时间复杂度 O(min(n^2/3,m^1/2)m)(可大致记为O(n^2*m))
首先同样是进行增广路,dinic则不是任意选择增广。
首先它进行bfs遍历所有可以遍历的点(剩余流量为0的边不可经过),并且标出层数(根据bfs时的遍历层数决定)
然后在用dfs进行增广,此时在增广时,限制流向只能是第i层的点流向第i+1层。因此我们可以从一个点同时向多个点增广并且保证不产生回流。
并且dfs中对于每个点访问邻接表是初始i=cur[u]而不是通常fir[u]保证已经被增广过的边不再被增广。因为此处cur[u]会随i改变,再次访问该节点时起始点也会改变。
#include<cstdio> #include<queue> #include<cstring> using namespace std; const int N=1e4+5,inf=1e9; struct E{ int v,q,n; }e[N*20]; int fir[N],s=1,n,m,sp,cp,dep[N],cur[N]; queue<int>dl; void add(int u,int v,int q){ e[++s].v=v; e[s].q=q; e[s].n=fir[u]; fir[u]=s; } bool bfs(){ memset(dep,0,sizeof(dep)); dl.push(sp);dep[sp]=1; while(!dl.empty()){ int u=dl.front();dl.pop(); for(int i=fir[u];i;i=e[i].n) if(e[i].q&&!dep[e[i].v]){ dep[e[i].v]=dep[u]+1; dl.push(e[i].v); } } return dep[cp]; } int dfs(int u,int a){ if(!a||u==cp) return a; int fl=0,f; for(int& i=cur[u];i;i=e[i].n) if(e[i].q&&dep[u]+1==dep[e[i].v]&&(f=dfs(e[i].v,min(a-fl,e[i].q)))){ fl+=f; e[i].q-=f; e[i^1].q+=f; } return fl; } int main(){ int u,v,q,ans=0; scanf("%d%d%d%d",&n,&m,&sp,&cp); for(int i=1;i<=m;++i){ scanf("%d%d%d",&u,&v,&q); add(u,v,q),add(v,u,0); } while(bfs()){ for(int i=1;i<=n;++i) cur[i]=fir[i]; ans+=dfs(sp,inf); } printf("%d",ans); return 0; }
而且最大流还能求解二分图匹配问题。
只需要建立一个超级源点s,s向每一个a集合的点连一条流量为1的边,建立超级汇点t,每一个b集合的点向t连接一条流量为1的边。
并且从a集合每个点向它能匹配的b集合的点连一条流量为1的边。
之后求最大流。
例题:poj1469:http://poj.org/problem?id=1469
题目大意:
有t组数据
a集合有p个点,b集合有m个点
p行每行第一个数s s后为这一行对应的a集合的点能匹配的b集合的点
问a集合的点能不能完美匹配。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=405; struct X { int v,sy,n,f; }x[70005]; int n,s=1,pre[N],cs[N],q[N]; bool vis[N]; void add(int u,int v,int sy) { x[++s].v=v; x[s].n=x[u].f; x[x[u].f=s].sy=sy; } bool bfs() { memset(vis,0,sizeof(vis)); vis[1]=q[0]=1; for(int t=0,w=1;t<w;++t) for(int i=x[q[t]].f;i;i=x[i].n) if(!vis[x[i].v]&&x[i].sy) { vis[x[i].v]=1; cs[x[i].v]=cs[q[t]]+1; q[w++]=x[i].v; } return vis[n]; } int dfs(int u,int a) { if(u==n||!a) return a; int fl=0,f; for(int &i=pre[u];i;i=x[i].n) if(cs[u]+1==cs[x[i].v]&&(f=dfs(x[i].v,min(a,x[i].sy)))) { x[i].sy-=f; x[i^1].sy+=f; fl+=f; a-=f; } return fl; } int main() { int a,b,ans,t,c; scanf("%d",&t); while(t--) { scanf("%d%d",&a,&b); n=a+b+2;ans=0; s=1; memset(x,0,sizeof(x)); for(int i=1;i<=a;++i) add(1,i+1,1),add(i+1,1,0); for(int i=1;i<=b;++i) add(i+a+1,n,1),add(n,i+a+1,0); for(int i=1;i<=a;++i) { scanf("%d",&b); while(b--) { scanf("%d",&c); add(i+1,c+a+1,1); add(c+a+1,i+1,0); } } while(bfs()) { for(int i=1;i<=n;++i) pre[i]=x[i].f; ans+=dfs(1,1000000000); } ans==a?printf("YES "):printf("NO "); } return 0; }
最小费用最大流
在原来的基础上,每条边多了一个新的权值,费用w,这条边每次流过1的流量时,就会产生费用w。
然后让你在维持最大流的前提下,使得费用尽可能小
ek+spfa
简单来说就是ek找增广路的时候,优先根据费用w所得最短路增广。
#include<cstdio> #include<queue> #include<cstring> using namespace std; const int N=5005; struct E{ int v,q,n,w; }e[N*20]; int fir[N],s=1,st,ed,fl[N],pre[N],dis[N]; bool vis[N]; queue<int>dl; void add(int u,int v,int q,int w){ e[++s].v=v; e[s].n=fir[u]; fir[u]=s; e[s].q=q; e[s].w=w; } bool bfs(){ memset(fl,0,sizeof(fl)); fl[st]=1e9; memset(dis,0x3f,sizeof(dis)); dl.push(st);dis[st]=0; while(!dl.empty()){ int u=dl.front();dl.pop(); for(int i=fir[u];i;i=e[i].n) if(e[i].q&&dis[e[i].v]>dis[u]+e[i].w){ dis[e[i].v]=dis[u]+e[i].w; fl[e[i].v]=min(fl[u],e[i].q); pre[e[i].v]=i^1; if(!vis[e[i].v]){ vis[e[i].v]=1; dl.push(e[i].v); } } vis[u]=0; } return fl[ed]; } int main(){ int n,m,ans=0,ans1=0,u,v,q,w; scanf("%d%d%d%d",&n,&m,&st,&ed); while(m--){ scanf("%d%d%d%d",&u,&v,&q,&w); add(u,v,q,w),add(v,u,0,-w); } while(bfs()){ ans+=fl[ed]; ans1+=fl[ed]*dis[ed]; for(int i=ed;i!=st;i=e[pre[i]].v){ e[pre[i]].q+=fl[ed]; e[pre[i]^1].q-=fl[ed]; } } printf("%d %d",ans,ans1); return 0; }
dinic+spfa
简单来说就是dinic标记层数改为根据最短路的dis数组标记(做spfa时反向跑,这样就能求出所有点到汇点的最短距离)
然后dfs增广时只经过那些构成最短路的边
#include<cstdio> #include<queue> #include<cstring> using namespace std; const int N=5005,inf=1e9; typedef long long ll; struct E{ int v,n,q,w; }e[N*20]; int n,m,sp,cp,s=1,fir[N],dis[N],ans1,cur[N]; ll ans; bool vis[N]; queue<int>dl; void add(int u,int v,int q,int w){ e[++s].v=v; e[s].q=q; e[s].w=w; e[s].n=fir[u]; fir[u]=s; } bool spfa(){ dl.push(cp); vis[cp]=1; memset(dis,0x3f,sizeof(dis)); dis[cp]=0; while(!dl.empty()){ int u=dl.front();dl.pop(); for(int i=fir[u];i;i=e[i].n) if(e[i^1].q&&dis[e[i].v]>dis[u]-e[i].w){ dis[e[i].v]=dis[u]-e[i].w; if(!vis[e[i].v]){ vis[e[i].v]=1; dl.push(e[i].v); } } vis[u]=0; } return dis[sp]<1061109567; } int dfs(int u,int a){ vis[u]=1; if(!a||u==cp) return a; int fl=0,f; for(int& i=cur[u];i;i=e[i].n) if(e[i].q&&!vis[e[i].v]&&dis[u]-e[i].w==dis[e[i].v]&&(f=dfs(e[i].v,min(e[i].q,a-fl)))){ e[i].q-=f; e[i^1].q+=f; ans+=(ll)f*e[i].w; fl+=f; } return fl; } int main(){ int u,v,q,w; scanf("%d%d%d%d",&n,&m,&sp,&cp); while(m--){ scanf("%d%d%d%d",&u,&v,&q,&w); add(u,v,q,w),add(v,u,0,-w); } while(spfa()){ vis[cp]=1; while(vis[cp]){ memset(vis,0,sizeof(vis)); for(int i=1;i<=n;++i) cur[i]=fir[i]; ans1+=dfs(sp,inf); } memset(vis,0,sizeof(vis)); } printf("%d %lld",ans1,ans); return 0; }