最大流ek算法和dinic算法总结
n为节点数量,m为边数量
EK算法复杂度:O(n*m^2)
dinic算法复杂度:O(n^2*m)
EK算法思想就是先用bfs找到一条增广路(从源点到汇点有流量的路),然后用pre数组记录这条路径上每一个节点的上一个节点。之后利用pre数组完成对这条路上所有边流量的消减,以及增加答案。看代码就清楚了,不复杂
例题:https://www.luogu.com.cn/problem/P3376
EK算法:
#include <stdio.h> #include <iostream> #include <string.h> #include <vector> #include <set> #include <queue> #include <map> #include <algorithm> #define lson rt << 1 #define rson rt << 1 | 1 using namespace std; typedef long long ll; const int INF = 0x3f3f3f3f; const int maxm = 20000 + 10; //边的数量 const int maxn = 200+10; //点的数量 ll n, m, st, en,dis[maxn],w[maxn][maxn],pre[maxn],minn[maxn]; vector<ll>e[maxn]; queue<ll>r; ll bfs(){ while(!r.empty()) r.pop(); memset(dis,0,sizeof(dis)); r.push(st); dis[st]=1; minn[st]=INF; while(!r.empty()){ ll u=r.front(); r.pop(); ll len=e[u].size(); for(ll i=0;i<len;i++){ ll v=e[u][i]; if(!dis[v] && w[u][v]>0){ minn[v]=min(minn[u],w[u][v]); pre[v]=u; dis[v]=1; if(v==en) return 1; r.push(v); } } } return 0; } ll dfs(){ ll res=0; ll x=en; while(x!=st){ ll p=pre[x]; w[x][p]+=minn[en]; w[p][x]-=minn[en]; x=p; } res+=minn[en]; return res; } void init(){ memset(w,0,sizeof(w)); } int main() { init(); scanf("%lld%lld%lld%lld", &n, &m,&st,&en); while (m--) { ll u,v,ww; scanf("%lld%lld%lld",&u,&v,&ww); e[u].push_back(v); e[v].push_back(u); w[u][v]+=ww; } ll res=0; while(bfs()){ res+=dfs(); } printf("%lld\n",res); return 0; }
EK算法另外一种
// #include <stdio.h> //这个是链表版,这道题卡内存不能用链表 // #include <iostream> // #include <string.h> // #include <vector> // #include <set> // #include <queue> // #include <map> // #include <algorithm> // #define lson rt << 1 // #define rson rt << 1 | 1 // using namespace std; // typedef long long ll; // const int INF = 0x3f3f3f3f; // const int maxm = 20000 + 10; //边的数量 // const int maxn = 200+10; //点的数量 // struct Edge // { // int v, w,next; // } e[maxm]; // int cnt, n, m, st, en,head[maxn],dis[maxn]; // queue<int>r; // inline void add_edge(int u,int v,int w){ // e[cnt].v=v; // e[cnt].w=w; // e[cnt].next=head[u]; // head[u]=cnt++; // e[cnt].v=u; // e[cnt].w=0; // e[cnt].next=head[v]; // head[v]=cnt++; // } // int bfs(){ // while(!r.empty()) r.pop(); // memset(dis,0,sizeof(dis)); // r.push(st); // dis[st]=1; // while(!r.empty()){ // int u=r.front(); // r.pop(); // for(int i=head[u];i!=-1;i=e[i].next){ // int v=e[i].v; // if(!dis[v] && e[i].w>0){ // dis[v]=dis[u]+1; // if(v==en) return 1; // r.push(v); // } // } // } // return 0; // } // int dfs(int u,int flow){ // if(u==en) return flow; // int k,tmp=flow; // for(int i=head[u];i!=-1;i=e[i].next){ // int v=e[i].v; // if(dis[v]==dis[u]+1 && e[i].w>0){ // k=dfs(v,min(tmp,e[i].w)); // if(!k) dis[v]=0; // tmp-=k;e[i].w-=k;e[i^1].w+=k; // if(!tmp) break; // } // } // return flow-tmp; // } // void init(){ // cnt=0; // memset(head,-1,sizeof(head)); // } // int main() // { // init(); // scanf("%d%d%d%d", &n, &m,&st,&en); // while (m--) // { // int u,v,w; // scanf("%d%d%d",&u,&v,&w); // add_edge(u,v,w); // } // int res=0; // while(bfs()){ // res+=dfs(st,INF); // } // printf("%d\n",res); // return 0; // } #include <stdio.h> #include <iostream> #include <string.h> #include <vector> #include <set> #include <queue> #include <map> #include <algorithm> #define lson rt << 1 #define rson rt << 1 | 1 using namespace std; typedef long long ll; const int INF = 0x3f3f3f3f; const int maxn = 200 + 10; //点的数量 ll n, m, st, en, dis[maxn], w[maxn][maxn]; vector<int> e[maxn]; queue<ll> r; ll bfs() { while (!r.empty()) r.pop(); memset(dis, 0, sizeof(dis)); r.push(st); dis[st] = 1; while (!r.empty()) { ll u = r.front(); r.pop(); ll len = e[u].size(); for (ll i = 0; i < len; i++) { ll v = e[u][i]; if (!dis[v] && w[u][v] > 0) { dis[v] = dis[u] + 1; if (v == en) return 1; r.push(v); } } } return 0; } ll dfs(ll u, ll flow) { if (u == en) return flow; ll k, tmp = flow; ll len = e[u].size(); for (ll i = 0; i < len; i++) { ll v = e[u][i]; if (dis[v] == dis[u] + 1 && w[u][v] > 0) { k = dfs(v, min(tmp, w[u][v])); if (!k) dis[v] = 0; tmp -= k; w[u][v] -= k; w[v][u] += k; if (!tmp) break; } } return flow - tmp; } void init() { memset(w, 0, sizeof(w)); } int main() { init(); scanf("%lld%lld%lld%lld", &n, &m, &st, &en); while (m--) { ll u, v, ww; scanf("%lld%lld%lld", &u, &v, &ww); w[u][v] += ww; e[u].push_back(v); e[v].push_back(u); } ll res = 0; while (bfs()) { res += dfs(st, INF); } printf("%lld\n", res); return 0; }
dinic算法:
它就是利用bfs对图进行一个分层,然后通过dfs来增广.增广过程除了当前节点x外,还需要传入一个表示“目前为止所有边的最小残量”的limit,当前节点x为汇点或者limit==0时终止dfs过程,否则会多路增广。还有在dfs过程中的一个重要优化:保存每个节点x正在遍历的子边保存在cur[x]以免重复计算遍历过的子边.
cur数组原理是什么呢?其实就是当我们在对一个节点u(假设有多个儿子)进行增广时,把他的儿子1,2的可用流量都用完了,那么在下一次dfs模块走到u时,我们如果可以直接从儿子3开始进行增广就可以大大减少额外的时间开销
cur数组实现:我们可以在dfs里面做一点改动,即先定义一个cur数组,在dfs之前把邻接表的head数组拷贝到cur里面,然后在遍历u的时候同时把cur里面的边的下标后移,以达到将用完的边略去的目的
例题:POJ 3281 Dining
dinic算法:
#include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> #include<queue> using namespace std; const int maxn=10000; const int INF=0x3f3f3f3f; int head[maxn],cnt,st,en,dis[maxn],cur[maxn]; struct edge { int v,next,c,flow; }e[maxn]; void add_edge(int x,int y,int z) { e[cnt].v=y; e[cnt].c=z; e[cnt].flow=0; e[cnt].next=head[x]; head[x]=cnt++; } bool bfs() { memset(dis,0,sizeof(dis)); dis[st]=1; queue<int>r; r.push(st); while(!r.empty()) { int x=r.front(); r.pop(); for(int i=head[x];i!=-1;i=e[i].next) { int v=e[i].v; if(!dis[v] && e[i].c>e[i].flow) { dis[v]=dis[x]+1; r.push(v); } } } return dis[en]; } int dinic(int s,int limit) { if(s==en || !limit) return limit; int ans=0; for(int &i=cur[s];i!=-1;i=e[i].next) { int v=e[i].v,feed; if(dis[v]!=dis[s]+1) continue; feed=dinic(v,min(limit,e[i].c-e[i].flow)); if(feed) { e[i].flow+=feed; e[i^1].flow-=feed; limit-=feed; ans+=feed; if(limit==0) break; } } if(!ans) dis[s]=-1; return ans; } int main() { memset(head,-1,sizeof(head)); int n,f,d; scanf("%d%d%d",&n,&f,&d); st=0; en=2*n+f+d+1; for(int i=1;i<=f;++i) { add_edge(st,2*n+i,1); add_edge(2*n+i,st,0); } for(int i=1;i<=d;++i) { add_edge(2*n+f+i,en,1); add_edge(en,2*n+f+i,0); } for(int i=1;i<=n;++i) { add_edge(i,n+i,1); add_edge(n+i,i,0); int sum1,sum2; scanf("%d%d",&sum1,&sum2); int x; for(int j=0;j<sum1;++j) { scanf("%d",&x); add_edge(x+2*n,i,1); add_edge(i,x+2*n,0); } for(int j=0;j<sum2;++j) { scanf("%d",&x); add_edge(n+i,x+f+2*n,1); add_edge(x+f+2*n,n+i,0); } }//主函数从开头到这就是建图 int ans=0; while(bfs()) { for(int i=0;i<=en;i++) cur[i]=head[i]; ans+=dinic(st,1); //这个1也可以改成无穷大 } printf("%d\n",ans); return 0; }