无源汇上下界可行流
题目描述
核心思路
这个题目,给出了容量的下界和上界,这使得与普通的网络流不同。设容量下界为 C l ( u , v ) C_l(u,v) Cl(u,v),容量上界为 C u ( u , v ) C_u(u,v) Cu(u,v),边 ( u , v ) (u,v) (u,v)上的流量为 f ( u , v ) f(u,v) f(u,v),要求 C l ( u , v ) ≤ f ( u , v ) ≤ C u ( u , v ) C_l(u,v)\leq f(u,v)\leq C_u(u,v) Cl(u,v)≤f(u,v)≤Cu(u,v)
我们令该式子都减去 C l ( u , v ) C_l(u,v) Cl(u,v),则得到 0 ≤ f ( u , v ) − C l ( u , v ) ≤ C u ( u , v ) − C l ( u , v ) 0\leq f(u,v)-C_l(u,v)\leq C_u(u,v)-C_l(u,v) 0≤f(u,v)−Cl(u,v)≤Cu(u,v)−Cl(u,v)。那么就转换为普通网络流的容量限制了。但是此时构建的这张新图中,并不一定满足流量守恒。
可行流算法的核心是将一个不满足流量守恒的初始流调整成满足流量守恒的流
初始流即一开始将每条边流量设为 l o w ( i ) low(i) low(i)的流。显然这个流不一定满足流量守恒。我们要通过调整(再加上一个附加流)将其变为可行流。
设 ∑ i = u f i n ( i ) \sum _{i=u} \limits f_{in}(i) i=u∑fin(i)表示到达点 u u u的流入量, ∑ i = u f o u t ( i ) \sum _{i=u} \limits f_{out}(i) i=u∑fout(i)表示从点 u u u出去的流出量,我们在原图中,令 A [ u ] = A[u]= A[u]= ∑ i = u f i n ( i ) − \sum _{i=u} \limits f_{in}(i)- i=u∑fin(i)− ∑ i = u f o u t ( i ) \sum _{i=u} \limits f_{out}(i) i=u∑fout(i),即初始流中,点 u u u的流入量-流出量
在附加流中,让点 u u u的流入量-流出量= − A [ u ] -A[u] −A[u],就能保证流量守恒了。
- 在附加流中,当 − A [ u ] < 0 -A[u]<0 −A[u]<0时,即 A [ u ] > 0 A[u]>0 A[u]>0,说明在新图中,点 u u u的流入量小于点 u u u的流出量,那么在新图中我们就需要增加点 u u u的流入量,要增加的流入量为 A [ u ] A[u] A[u]。那么我们就需要增加附加边,使得点 u u u的流入量等于点 u u u的流出量。因此,我们可以新建一个超级源点 S S S,从 S S S直接连一条容量为 A [ u ] A[u] A[u]的有向边到点 u u u,即 S → u S\to u S→u。
- 在附加流中,当 − A [ u ] > 0 -A[u]>0 −A[u]>0,即 A [ u ] < 0 A[u]<0 A[u]<0,说明在新图中,点 u u u的流入量大于点 u u u的流出量,那么在新图中我们就需要增加点 u u u的流出量,要增加的流出量为 − A [ u ] -A[u] −A[u](因为 A [ u ] A[u] A[u]是负数,我们增加的流量不能是负值)。那么我们就需要增加附加边,使得点 u u u的流入量等于点 u u u的流出量。因此,我们可以新建一个超级汇点 T T T,从点 u u u直接连一条容量为 − A [ u ] -A[u] −A[u]的有向边到汇点 T T T,即 u → T u\to T u→T
如下图理解:
接着:
算法设计:
step1:
建立新图G’。新边容量为d-c(上界-下界)
S = 0, T = n+1;
for (int i = 0; i < m; i ++ )
{
int a, b, c, d;
cin >> a >> b >> c >> d;
add(a, b, c, d);
A[b] += c, A[a] -= c;
}
step2:
建立超级源点S和超级汇点T。流入大于流出点,连S到i,容量为A[i];流出小于流入点,连i到T,容量为-A[i]
int tot = 0;
for (int i = 1; i <= n; i ++ )
if (A[i] > 0) add(s, i, 0, A[i]), tot += A[i];
else if (A[i] < 0) add(i, t, 0, -A[i]);
step3:
判断流量守恒条件dinic() == tot。所有附加边的最大流满流,说明原图存在可行流(证明不会qaq,可以参考上图理解…)
if (tot != dinic()) puts("NO");
step4:
由于我们在新图 G ′ G' G′中每条边都减去了该边的最小下界,因此在输出原图可行流时,是f[i^1]再加上最小下界 l [ i ] l[i] l[i]
这里反向边的容量f[i^1]的值是正向边f[i]的流量值, i i i在附加流中的流量(即反边容量)。这里主要是运用残留网络中双向边的含义。
残留网络中的反向边f[i^1]表示的是原图中的正向边,而原图中正向边的含义就是这条边的流量。
else
{
puts("YES");
for (int i = 0; i < m*2; i += 2)
cout << f[i^1] + l[i] << endl;
}
注意边数的计算:题目描述中给出了最终有 10200 10200 10200条边,由于我们需要增加从超级源点S到这 200 200 200个点的边已经从这 200 200 200个点到超级汇点 T T T的边,在构建残留网络时,需要双向边,因此总共的边数就是 M = ( 10200 + N ) × 2 M=(10200+N)\times2 M=(10200+N)×2
代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=210,M=(10200+N)*2,INF=1e8;
int n,m,S,T;
int h[N],e[M],ne[M],f[M],l[M],idx; //l[]数组存储的是每条边的流量下界
int q[N],d[N],cur[N],A[N]; //A[i]表示第i个点的流入量与流出量的差值
void add(int a,int b,int c,int d)
{
//第idx条边的容量f[idx]为d-c 流量下界l[idx]是c
e[idx]=b,f[idx]=d-c,l[idx]=c,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
int find(int u,int limit)
{
if(u==T)
return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
int ver=e[i];
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)
d[ver]=-1;
f[i]-=t,f[i^1]+=t,flow+=t;
}
}
return flow;
}
bool bfs()
{
memset(d,-1,sizeof d);
int hh=0,tt=0;
q[0]=S,d[S]=0,cur[S]=h[S];
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)
return true;
q[++tt]=ver;
}
}
}
return false;
}
int dinic()
{
int maxflow=0;
int flow;
while(bfs())
{
while(flow=find(S,INF))
maxflow+=flow;
}
return maxflow;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
S=0,T=n+1;
for(int i=0;i<m;i++)
{
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
add(a,b,c,d);
//从a流向b 每条边都减去它的流量下界
//因此节点a减少了c
//节点b增加了c
A[a]-=c,A[b]+=c;
}
int tot=0; //记录从超级源点S到当前点i的流量
for(int i=1;i<=n;i++)
{
if(A[i]>0)//如果节点i的流入量与流出量的差值大于0
{
//从超级源点S到节点i添加一条 流量下界为0 流量上界为A[i] 的附加边
add(S,i,0,A[i]);
tot+=A[i];
}
else if(A[i]<0) //如果节点i的流入量与流出量的差值小于0
{
//从节点i到超级汇点T添加一条 流量下界为0 流量上界为-A[i] 的附加边
add(i,T,0,-A[i]);
}
}
//用dinic算法在新图中求出最大流后,如果这个最大流不等于tot
//则说明不满足"所有附加边的最大流满流,说明原图存在可行流"
if(dinic()!=tot)
puts("NO");
else
{
puts("YES");
//i+=2是因为一条边分成了两条方向相反的有向边 i+=2就可以直接枚举到下一条边
//即(0,1)是一条边,其中0表示一条有向边,1表示与编号为0的方向相反的边 (2,3)是一条边
//i+=2那么下次就可以枚举(2,3)这条边了
for(int i=0;i<m*2;i+=2)
printf("%d\n",f[i^1]+l[i]);
}
return 0;
}