FF方法的最简单实现,每次寻找增广路都是朴素的BFS。时间复杂度O(N*M^2)。
模板代码:所谓距离标号就是最少经过多少条有容量的边能到达汇点,假如距离标号数组为lv[],找增广路找到u点的时候都按照lv[u]==lv[v]+1找出v点扩展,满足这个条件的边称为可行弧,证明什么的就不说了。距离标号可以在算法开始之前进行一次BFS把初始标号预处理出来,这样有利于常数上的优化,也可以不预处理先直接清0后面再维护。关于距离标号的维护,由于增广时都走容量大于0的可行弧,如果没有容量大于0的边了那这个点差不多就废了,距离标号为n用于断层优化;如果还有容量大于0的边但没有可行弧那u的lv就取可扩展点的最小lv+1。SAP可以加很多常数上的优化,优化全加上后效果似乎很好,比如上面的BFS预处理。除了这个以外还有三个很重要的优化就是当前弧优化,断层优化,多路增广优化。
- 当前弧优化就是记录每个节点邻接表中从哪条边开始是当前正在增广或者可能可以增广的,作用就是处理回退边的时候可以很方便,找增广路的时候直接从当前弧开始找就少循环几遍,随便yy一下都能明白当前弧以前的边都是不会再走的了,所以当前弧优化的正确性是显然的。
- 断层优化就跟上面提到的一个点再也没有边可以增广有关,如果由于多次废点的产生而导致某一个标号的数量减少到0,那就意味着出现了断层,源点再也不可能到达汇点,可以直接退出循环,而如果源点也成为废点那也可以结束了。
- 多路增广优化就是每次找到增广路之后都不回退到源点重新开始找增广路,而是回退到最前面的限流边,然后继续增广,这个应该很好理解没啥好说的。
const int maxn=100010; const int maxm=maxn<<1; const int INF=0x3f3f3f3f; int gap[maxn],lv[maxn],pre[maxn],cur[maxn]; struct edge { int v,w,next; }e[maxm]; int h[maxn],ecnt; inline void init() { memset(h,-1,sizeof(h)); ecnt=0; } inline void addedge(int u,int v,int w) { e[ecnt].v=v; e[ecnt].w=w; e[ecnt].next=h[u]; h[u]=ecnt++; } inline void Addedge(int u,int v,int w) { addedge(u,v,w); // addedge(v,u,0);//directed addedge(v,u,w);//undirected } int sap(int n,int s,int t) { memcpy(cur,h,sizeof(h)); memset(gap,0,sizeof(gap)); int u=pre[s]=s; int ans=0,flow,neck; // memset(lv,0,sizeof(lv)); //nopre // gap[0]=n; for(int i=1;i<=n;i++)lv[i]=n;//bfspre gap[lv[t]=0]++; queue<int> q; q.push(t); while(!q.empty()) { int p=q.front();q.pop(); for(int i=h[p];~i;i=e[i].next) { if(lv[e[i].v]!=n)continue; // if(e[i].w)continue;//directed gap[lv[e[i].v]=lv[p]+1]++; q.push(e[i].v); } } while(lv[s]<n) { if(u==t) { flow=INF; for(int i=s;i!=t;i=e[cur[i]].v) { if(e[cur[i]].w<flow) { flow=e[cur[i]].w; neck=i; } } ans+=flow; for(int i=s;i!=t;i=e[cur[i]].v) { e[cur[i]].w-=flow; e[cur[i]^1].w+=flow; } u=neck; } for(int &i=cur[u];~i;i=e[i].next) if(e[i].w && lv[u]==lv[e[i].v]+1)break; if(~cur[u]) { pre[e[cur[u]].v]=u; u=e[cur[u]].v; } else { if(!--gap[lv[u]])break; cur[u]=h[u]; int mlv=n-1; for(int i=h[u];~i;i=e[i].next) { if(e[i].w && lv[e[i].v]<mlv) { mlv=lv[e[i].v]; cur[u]=i; } } gap[lv[u]=mlv+1]++; u=pre[u]; } } return ans; }
模板代码:每次增广之前都从源点开始BFS一次,这次是搞相对于源点的距离标号,一旦访问到汇点就说明可以增广,结束BFS并开始增广,如果BFS结束后都没有访问到汇点,那就不能再增广了,算法结束。增广则是使用DFS进行多路增广,实现起来十分方便给力。
const int maxn=210; const int maxm=maxn<<1; const int INF=0x3f3f3f3f; int lv[maxn]; struct edge { int v,w,next; }e[maxm]; int h[maxn],ecnt; inline void init() { memset(h,-1,sizeof(h)); ecnt=0; } inline void addedge(int u,int v,int w) { e[ecnt].v=v; e[ecnt].w=w; e[ecnt].next=h[u]; h[u]=ecnt++; } inline void Addedge(int u,int v,int w) { addedge(u,v,w); addedge(v,u,0);//directed // addedge(v,u,w);//undirected } bool bfs(int s,int t) { queue<int> q; q.push(s); memset(lv,-1,sizeof(lv)); lv[s]=0; while(!q.empty()) { int u=q.front();q.pop(); if(u==t)return 1; for(int i=h[u];~i;i=e[i].next) { int v=e[i].v; if(~lv[v] || !e[i].w)continue; lv[v]=lv[u]+1; q.push(v); } } return 0; } int dfs(int u,int flow,int t) { if(u==t)return flow; int ret=0,f; for(int i=h[u];~i;i=e[i].next) { int v=e[i].v; if(e[i].w && lv[u]+1==lv[v]) { f=dfs(v,min(flow-ret,e[i].w),t); e[i].w-=f; e[i^1].w+=f; ret+=f; if(ret==flow)return ret; } } return ret; } int dinic(int s,int t) { int ret=0; while(bfs(s,t))ret+=dfs(s,INF,t); return ret; }