算法问题描述
网络流就是网络流,无FUCK说
最大流最小割定理
证明:
∵对于任一割(S,T),S到T的流量f必定全部从这一割经过
∴|f|<=c(S,T)
又当图中不存在增光路时,一定存在一个割(S,T)的容量被流满
即|f|=c(S,T)
∴f是F集合中最大的,c(S,T)是集合C中最小的,且f=c(S,T)
于是可以得到最小割等于最大流,且增光路算法可以找到最小割,也就可以得到最大流。
朴素FF算法
考虑贪心DFS找有残余流量的一条S->T增光路,但该方法容易找到反例。
考虑给算法反悔的方式,每次走完u->v,u-v剩余容量-=w,加一条反边v->u,其容量+=w,做同样的寻找。这可以理解为下次走x->v->u->y的时候把流量从v退到u再从u流到y,同时用x到v的流量填补v的入流也就是本来v->u的流量。
Dinic算法
算法思想
考虑朴素算法DFS找增光路每条边利用一次有点浪费,于是用BFS改进,把找增广路变成找增广网。
先用BFS给网络分层,形成一条一条可走的路径,同时判断能不能走到T
DFS找增广网,因为是网,所以到底传回来了多少流量是要记flu+=dw的!!!!!
弧优化
因为走u-v1的时候就已经把u-v1流量榨干了,下次走u直接跳到下一个v2,同时如果走完u没能把上次传下来的最小流量用完也就是在u出现了更窄的口子这次增广中也不要再走u了。算法实现上前者利用head的替身cur,后者令level_u=0。
代码
bool BFS(int s,int t){
memset(lev,0,sizeof lev);
for(int i=1;i<maxn;i++) cur[i]=head[i];
lev[s]=1;
queue<int> Q;
Q.push(s);
while(!Q.empty()){
int u=Q.front(); Q.pop();
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(!lev[v] && w){
Q.push(v);
lev[v]=lev[u]+1;
if(v==t) return 1;
}
}
}
return 0;
}
int DFS(int u,int minw,int t){
int flu=0;
if(u==t || !minw) return minw;
for(int &i=cur[u];i!=-1;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(lev[v]==lev[u]+1 && w){
int dw=DFS(v,min(minw-flu,w),t);
e[i].w-=dw;
e[i^1].w+=dw;
flu+=dw;
if(flu==minw) break;
}
}
if(flu!=minw) lev[u]=0;
return flu;
}
LL Dinic(int s,int t){
LL ans=0;
while(BFS(s,t)) ans+=DFS(s,INF,t);
return ans;
}
ISAP算法
算法思想
和Dinic同样基于分层的思想,维护d[i]为结点i到结点T的最短距离,每次走d[u]==d[v]+1的边,在跑增光路的过程中动态更新d[i]的值,从而节省BFS的时间。
小优化
记num[i]为d为i的结点个数,出现断层时退出
当前弧优化照样,在走不动的时候重置
代码
void BFS(int s,int t){
queue<int> Q;
Q.push(t);
d[t]=0;
while(!Q.empty()){
int u=Q.front(); Q.pop();
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].u;
if(d[v] || v==t) continue; //易错点
d[v]=d[u]+1;
Q.push(v);
}
}
}
long long Augument(int s,int t){
int u=t,minw=INF;
while(u!=s){
int w=edge[p[u]].w;
minw=min(minw,w);
u=edge[p[u]].u;
}
u=t;
while(u!=s){
edge[p[u]].w-=minw;
edge[p[u]^1].w+=minw;
u=edge[p[u]].u;
}
return minw;
}
long long ISAP(int s,int t){
long long ans=0;
BFS(s,t);
for(int i=1;i<=n;i++) cur[i]=head[i];
memset(num,0,sizeof(num));
for(int i=1;i<=n;i++) num[d[i]]++;
int u=s;
while(d[s]<n){
if(u==t){
ans+=Augument(s,t);
u=s;
}
bool flag=0;
for(int& i=cur[u];i!=-1;i=edge[i].next){
int v=edge[i].v,w=edge[i].w;
if(w && d[u]==d[v]+1){
flag=1;
p[u=v]=i;
break;
}
}
if(!flag){
int m=n-1;
if(--num[d[u]]==0) break;
for(int i=head[u];i!=-1;i=edge[i].next){
int w=edge[i].w,v=edge[i].v;
if(w) m=min(m,d[v]);
}
num[d[u]=m+1]++;
cur[u]=head[u];
if(u!=s) u=edge[p[u]].u; //易错点
}
}
return ans;
}