概念
1.1 流网络,不考虑反向边
1.2 可行流,不考虑反向边
1.2.1 两个条件:容量限制、流量守恒
1.2.2 可行流的流量指从源点流出的流量 - 流入源点的流量
1.2.3 最大流是指最大可行流
1.3 残留网络,考虑反向边,残留网络的可行流f’ + 原图的可行流f = 原题的另一个可行流
(1) |f’ + f| = |f’| + |f|
(2) |f’| 可能是负数
1.4 增广路径
1.5 割
1.5.1 割的定义
1.5.2 割的容量,不考虑反向边,“最小割”是指容量最小的割。
1.5.3 割的流量,考虑反向边,f(S, T) <= c(S, T)
1.5.4 对于任意可行流f,任意割[S, T],|f| = f(S, T)
1.5.5 对于任意可行流f,任意割[S, T],|f| <= c(S, T)
1.5.6 最大流最小割定理
(1) 可以流f是最大流
(2) 可行流f的残留网络中不存在增广路
(3) 存在某个割[S, T],|f| = c(S, T)
EK算法
链式前向星初始化-1版本 0正边 1反边
S源点 T汇点
d[]
流量pre[]
前向边
存图存的是残留网络
时间复杂度: O ( n m 2 ) O(nm^2) O(nm2)
const int N=1010,M=20010;
int h[N],e[M],ne[M],f[M],idx;
int d[N],pre[N];
bool st[N];
int n,m,S,T;
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],f[idx]=c;h[a]=idx++;
e[idx]=a,ne[idx]=h[b],f[idx]=0,h[b]=idx++;
}
bool bfs()
{
memset(st,0,sizeof st);
queue<int> q;
q.push(S);
st[S]=1;
d[S]=0x3f3f3f3f;
while(q.size())
{
int t=q.front();q.pop();
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(!st[j]&&f[i])
{
st[j]=1;
d[j]=min(d[t],f[i]);
pre[j]=i;
if(j==T) return 1;
q.push(j);
}
}
}
return 0;
}
int EK()
{
int r=0;
while(bfs())
{
r+=d[T];
for(int i=T;i!=S;i=e[pre[i]^1])
f[pre[i]]-=d[T],f[pre[i]^1]+=d[T];
}
return r;
}
Dinic算法
模拟队列
时间复杂度: O ( n 2 m ) O(n^2m) O(n2m)
int h[N],e[M],ne[M],f[M],idx;
int S,T,d[N],q[N],cur[N];
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],f[idx]=c,h[a]=idx++;
e[idx]=a,ne[idx]=h[b],f[idx]=0,h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
int tt=0,hh=0;
q[S]=0,cur[S]=h[S],d[S]=0;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(d[j]==-1&&f[i])
{
d[j]=d[t]+1;
cur[j]=h[j];
if(j==T) return 1;
q[++tt]=j;
}
}
}
return 0;
}
int dfs(int u=S,int flow=0x3f3f3f3f)
{
if(u==T) return flow;
int rmn=flow;// 剩余流量
for(int &i=cur[u];i!=-1&&rmn;i=ne[i])// 当前弧优化
{
int j=e[i];
if(d[j]==d[u]+1&&f[i])
{
int t=dfs(j,min(f[i],rmn));
if(!t) d[j]=-1;// 优化
f[i]-=t,f[i^1]+=t,rmn-=t;
}
}
return flow-rmn;
}
int dinic()
{
int r=0;
while(bfs()) r+=dfs();
return r;
}