网络最大流建模
最大流问题一般求集合最优解,要求我们可以建立原问题和网络流集合的一一对应关系
在后续的方案输出中,一定要记得那些是原图的边,那些是残余网络中的边,针对问题进行不同的分析
对于所有可行解的集合P:
-
1,对于流网络的所有可行流的集合,对于可行解中的一个解,有且只有一个可行流与之符合
-
2,对于流网络中的任何一个可行流都能对应一个可行解
则可行解中的最大值等于可行流中的最大流
在模拟退火中我们也接触了集合最优,这类问题的证伪手段一般是局部左右不等于全局最优,是 OI 中及其常见的一种题型
一,二分图建模
1,最大匹配
从源点S到汇点T建立:
- 从源点S流向左集合 S 的容量为 1 的边
- 左集合到右集合的容量为 1 的边
- 从右集合到汇点T的容量为1的边
- 对于此流网络的最大流便是最大二分匹配
注意对分属于(二分图的)不同点集的点进行编号,使其编号不一
具体方案
- 找到真正二分图的匹配边,其实就是编号为偶数的边(存储边的成对变换)
- 看一下这边的残余容量是否为空,为空即为匹配成功,使用了这条边
int main()
{
scanf("%d%d", &m, &n);
memset(h, -1, sizeof h);
s=0; t=n+1;
int a, b;
while (cin>>a>>b,a!=-1&&b!=-1)add(a, b, 1);
rep(i,1,m)add(s,i,1);
rep(i,m+1,t-1) add(i,t,1);
printf("%d\n", Dinic());
for (int i = 0; i < idx; i += 2)
if (e[i] > m && e[i] <= n && !f[i])
printf("%d %d\n", e[i ^ 1], e[i]);
}
2,多重匹配
从源点S到汇点T建立:
- (匹配上限)从源点S流向左集合 S 的容量为 第一个集合同种类型点的数目 的边
- (匹配方式:一一匹配)左集合到右集合的容量为 1 的边
- (匹配上限)从右集合到汇点T的容量为1的边
- 对于此流网络的最大流便是最大二分匹配
int main()
{
cin>>m>>n;
memset(h, -1, sizeof h);
s=0; t=n+1+m;
int tot= 0;
rep(i,1,m)
{
int x; cin>>x; tot+=x;
add(s,i,x);
}
rep(i,m+1,t-1)
{
int c;
scanf("%d", &c);
add(i, t, c);
}
rep(i,1,m)
rep(j,m+1,t-1)
add(i, j, 1);
if (Dinic() != tot) puts("0");
else
{
puts("1");
rep(i,1,m)
{
for (int j = h[i]; ~j; j = ne[j])
if (e[j] > m && e[j] <= m + n && !f[j])
printf("%d ", e[j] - m);
puts("");
}
}
return 0;
}
二,有上下界网络流
1,无源汇上下界可行流
描述
给定一个包含 n n n 个点 m m m 条边的有向图,每条边都有一个流量下界和流量上界。
求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。
思路
- 图函数转化,虚拟源汇
- [ G , f ] [G,f] [G,f] 是原来的流网络和一个可行流网络,其中可行流 f f f 需要同时满足下界和上界
- [ G ′ , f ′ ] [G',f'] [G′,f′] 是一个构造流网络,这个图没有下界,只有上界,只要借助代数特征在两边同时减去下界即可
- 熟知: [ G ′ , f ′ ] [G',f'] [G′,f′] 的可行最大流 f ′ f' f′ 与 [ G , f ] [G,f] [G,f] 中的 可行流 f ′ f' f′ 一一对应
- 为了保证流量守恒,只须对每个点统计下界在出入抵消后的偏移量,多退少补,哪边大说明那边减的多,从源汇补偿即可
实现
- A 数组记录了哪边多,l 数组记录了每条边的下界(多用于方案输出中)
- S,T 是虚拟源汇
- 特殊的建边方案
void add(int a, int b, int c,int d)
{
l[idx] = c; e[idx] = b, f[idx] = d-c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
int main()
{
memset(h, -1, sizeof h);
read(n); read(m);
rep(i,1,m)
{
int a,b,c,d;
read(a); read(b); read(c); read(d);
add(a,b,c,d);
A[a]-=c;
A[b]+=c;
}
S = 0;
T = n+1;
int tot = 0;
rep(i,1,n)
{
if(A[i]>0) add(S,i,0,A[i]),tot+=A[i];
else if(A[i]<0) add(i,T,0,-A[i]);
}
if(Dinic()==tot)
{
puts("YES");
for(int i = 0;i <= m * 2-2; i+=2)
{
wri(f[i^1]+l[i]);
puts("");
}
}
else puts("NO");
return 0;
}
2,有源汇上下界最大流
- 先联通实际源汇,并对联通路实际源汇的新图里跑无源汇上下界最大流
- 删去实际源汇的边及其流量
- 在跑完的残留网络中求解实际源汇的最大流即为答案
int main()
{
memset(h, -1, sizeof h);
read(n); read(m);
read(s); read(t);
rep(i,1,m)
{
int a,b,c,d;
read(a); read(b); read(c); read(d);
add(a,b,c,d);
A[a]-=c;
A[b]+=c;
}
S = 0;
T = n+1;
add(t,s,0,INF);
int tot = 0;
rep(i,1,n)
{
if(A[i]>0) add(S,i,0,A[i]),tot+=A[i];
else if(A[i]<0) add(i,T,0,-A[i]);
}
if(Dinic()==tot)
{
S= s; T = t;
f[idx-1]=f[idx-2]= 0;
wri(Dinic());
}
else puts("No Solution");
return 0;
}
3,有源汇上下界最小流
之前已经证明过
∣
f
0
∣
+
∣
f
′
(
s
→
t
)
∣
=
f
(
s
→
t
)
|f_0|+|f′(s→t)|=f(s→t)
∣f0∣+∣f′(s→t)∣=f(s→t)
所以
-
f m a x ( s → t ) = ∣ f 0 ∣ + m a x f ′ ( s → t ) fmax(s→t)=|f_0|+maxf′(s→t) fmax(s→t)=∣f0∣+maxf′(s→t)
-
f m i n ( s → t ) = ∣ f 0 ∣ + m i n f ′ ( s → t ) fmin(s→t)=|f_0|+minf′(s→t) fmin(s→t)=∣f0∣+minf′(s→t)
-
由于 f ′ ( s → t ) = − f ′ ( t → s ) f′(s→t)=−f′(t→s) f′(s→t)=−f′(t→s),所以 m i n f ′ ( s → t ) = − m a x f ′ ( t → s ) minf′(s→t)=−maxf′(t→s) minf′(s→t)=−maxf′(t→s)
-
于是我们求出浸润流 f 0 f_0 f0 之后,在残量网络中求一遍 t → s t→s t→s的最大流
-
a n s = f 0 − d i n i c ( t , s ) ans=f_0−dinic(t,s) ans=f0−dinic(t,s)
int main()
{
memset(h, -1, sizeof h);
read(n); read(m);
read(s); read(t);
rep(i,1,m)
{
int a,b,c,d;
read(a); read(b); read(c); read(d);
add(a,b,c,d);
A[a]-=c;
A[b]+=c;
}
S = 0;
T = n+1;
int tot = 0;
rep(i,1,n)
{
if(A[i]>0) add(S,i,0,A[i]),tot+=A[i];
else if(A[i]<0) add(i,T,0,-A[i]);
}
add(t,s,0,INF);
if(Dinic()>=tot)
{
S= t; T = s;
tot = f[idx-1];
f[idx-1]=f[idx-2]= 0;
wri(tot - Dinic());
}
else puts("No Solution");
return 0;
}
三,多源汇(参考多重匹配)
int main()
{
int s_num,t_num;
memset(h, -1, sizeof h);
read(n); read(m);
read(s_num); read(t_num);
S = 0; T = n+1;
rep(i,1,s_num)
{ int id; read(id); add(S,id,INF); }
rep(i,1,t_num)
{ int id; read(id); add(id,T,INF); }
rep(i,1,m)
{
int a,b,c;
read(a); read(b); read(c);
add(a,b,c);
}
printf("%d",Dinic());
return 0;
}
四,最大流关键边
- 在最大流中拓宽一条边的了、容量可以使得整个最大流增大
- 通俗来讲就是瓶颈边
- 注意dfs联通性时不需要反向边,否则就一直在指自己
思路
-
判断是否是关键边,在于判断增加当前边容量后,是否存在从s->t的增广路。
-
对于 E ( u , v ) E(u,v) E(u,v)来说,只须判断从 s − > u s->u s−>u和从 v − > t v->t v−>t是否都存在流量大于 0 0 0的路
-
实现上只需要源汇同时走有容量的边,直到无法扩张,并枚举每条边判断是否满容且向两边能到源汇
void dfs_find(int u,bool st[],int op)
{
st[u] = 1 ;
mrep(i,u)
{
int v = e[i];
if(f[i^op] && !st[v])dfs_find(v,st,op);
}
}
int main()
{
memset(h, -1, sizeof h);
read(n); read(m);
S = 0;
T = n-1;
while (m--)
{
int a,b,c;
read(a); read(b); read(c);
add(a,b,c);
}
Dinic();
dfs_find(S,vis_s,0);
dfs_find(T,vis_t,1);
int res = 0;
for(int i= 0;i <=idx; i+= 2)
{
if(f[i]==0 && vis_s[e[i^1]] && vis_t[e[i]])res++;
}
wri(res);
return 0;
}
五,最大流判定
无向图网络流+二分答案
-
在判定性(连通性)的存边时,可以思考辅助 W 数组,表示容量上
限,然后在二分的时候根据lim 随时修改残余网络的01情况 -
所谓的两个方向建边其实就是把一条边
f
的双向值全部修改为容量即可(残余网络中为容量为0表示这条边不存在) -
无向图,要求每条边最多只能走一次,在建流网络的时候 ( u , v ) (u,v) (u,v)正向走一次, ( v , u ) (v,u) (v,u) 反向又走了一次,等价于没有流,把相应的边删去即可
void add (int a,int b,int c)
{
ne[idx]=h[a]; e[idx]=b; f[idx]=0; w[idx]=c; h[a]=idx++;
ne[idx]=h[b]; e[idx]=a; f[idx]=0; w[idx]=c; h[b]=idx++;
}
bool check(int lim)
{
rep(i,0,idx-1)
{
if(w[i]<=lim) f[i] = 1;
else f[i] = 0;
}
return Dinic() >= tar;
}
int main()
{
Mset(h,-1);
read(n); read(m); read(tar);
S = 1;
T = n;
int l = 1;
int r = 1e6;
rep(i,1,m)
{
int a,b,c;
read(a); read(b); read(c);
add(a,b,c);
}
while (l<r)
{
int mid = (l+r) >> 1;
if(check(mid)) r=mid;
else l =mid+1;
}
wri(r);
return 0;
}
六,拆点
- 对于在点上或者经过某个点的时候有限制的时候
- 将单独一个点拆成入点和出点,入点和出点之间的连边设置上限即可
- 形式上:
add (i,i+n,lim);
- 题面上:每个点只能匹配一个XX