无源汇有上下界可行流(也就是循环流)
模型:一个网络,求出一个流,使得每条边的流量必须>=Li且<=Hi,
每个点必须满足总流入量=总流出量(流量守恒)(这个流的特点是循环往复,无始无终)
可行流算法的核心是将一个不满足流量守恒的初始流调整成满足流量守恒的流
流量守恒,即每个点的总流入量=总流出量
如果存在一个可行流,那么一定满足每条边的流量都大于等于流量的下限
因此我们可以令每条边的流量等于流量下限,得到一个初始流,然后建出这个流的残量网络
(即:每条边的流量等于这条边的流量上限与流量下限之差)
这个初始流不一定满足流量守恒,因此最终的可行流一定是在这个初始流的基础上增大了一些边的流量使得所有点满足流量守恒
因此我们考虑在残量网络上求出一个另不满足流量守恒的附加流,
使得这个附加流和我们的初始流合并之后满足流量守恒
那么首先我们需要开一个数组A,A[i]表示i在初始流中的流入量-流出量的值
那么A[i]的正负表示流入量和流出量的大小关系,
A[i]>0,说明附加流的流入量要小于流出量
A[i]<0,说明附加流的流入量要大于流出量
所以A[i]=附加流流出量-流入量
下面就用A[i]表示初始流中i的流入量-流出量
但是dinic算法能够求的是满足流量守恒的有源汇最大流,
不能在原网络上直接求一个这样的无源汇且不满足流量守恒的附加流
注意到附加流是在原网络上不满足流量守恒的,这启发我们添加一些原网络之外的边和点,
用这些边和点实现“原网络上流量不守恒”的限制
具体地,如果一个点i在原网络上的附加流中需要满足流入量>流出量(A[i]<0),
那么我们需要给多的流入量找一个去处,因此我们建一条从i出发流量=|A[i]|的边
如果A[i]>0,也就是我们需要让附加流中的流出量>流入量,我们需要让多的流出量有一个来路,
因此我们建一条指向i的流量=|A[i]|的边.
当然,我们所新建的从i出发的边也要有个去处,指向i的边也要有个来路,
因此我们新建一个虚拟源点ss和一个虚拟汇点tt
(双写字母是为了和有源汇网络流中的源点s汇点t相区分)
新建的指向i的边都从ss出发,从i出发的边都指向tt
一个点要么有一条边指向tt,要么有一条边来自ss,
指向tt的边的总流量上限一定等于ss流出的边的总流量上限
因为每一条边对两个点的A[i]贡献一正一负大小相等,所以全部点的A[i]之和等于0,
即小于0的A[i]之和的绝对值=大于0的A[i]之和的绝对值.
如果我们能找到一个流满足新加的边都满流,这个流在原图上的部分就是我们需要的附加流
那么怎样找出一个新加的边都满流的流呢?
可以发现假如存在这样的方案,这样的流一定是我们所建出的图的ss-tt最大流,
所以跑ss到tt的最大流即可
如果最大流的大小等于ss出发的所有边的流量上限之和,则存在这样的附加流满足题意
最后,每条边在可行流中的流量=容量下界+附加流中它的流量(即跑完dinic之后所加反向边的权值)
————————————————
版权声明:本文为****博主「Coco_T_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.****.net/wu_tongtong/article/details/73320968
#include<cstdio>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<queue>
using namespace std;
const int maxn=;
const int inf=0x3f3f3f3f;
int head[maxn],level[maxn]; //前者为邻接表必要数据,后者为dinic的层 数据
int in[maxn]; //一条边的初始流量(最低值 也就是定为下界)
int limit[maxn]; //limit为该点的流量 小于0的时候是流出多
int bh[maxn]; //一条边的反向边的流量(正向边流出了多少,反向边就是多少)
//所以用初始的下界流量+上这个方向边的流量就是最终答案;
//那么为什么不直接正向呢???
//废话 正向求比较麻烦嘛。 原本的值是可以流的量,正向边流出去后
//那么答案就是可以流的量减去剩余的量就是答案;
//所以还不如直接求反向呢
int num; //邻接表
void init()
{
num=- ;
memset(head,-,sizeof(head));
}
struct node
{
int v,w,next;
}G[];
int bfs(int s,int t)
{
queue<int>q;
q.push(s);
memset(level,-,sizeof(level));
level[s]=;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i!=-;i=G[i].next){
int v=G[i].v;
if(G[i].w>&&level[v]==-){
level[v]=level[u]+;
q.push(v);
}
}
}
return level[t];
}
int dfs(int s,int t,int f)
{
if(s==t) return f;
int ans=;
for(int i=head[s];i!=-;i=G[i].next){
int v=G[i].v;
if(G[i].w>&&level[s]+==level[v]){
int d=dfs(v,t,min(G[i].w,f-ans));
if(d>){
G[i].w-=d;
G[i^].w+=d;
ans+=d;
if(ans==f) return ans;
}
}
}
return ans;
}
int dinic(int s,int t)
{
int ans=;
while(){
int temp=bfs(s,t);
if(temp==-) break;
ans+=dfs(s,t,inf);
}
return ans;
}
void build(int u,int v,int w)
{
num++;
G[num].v=v;
G[num].w=w;
G[num].next=head[u];
head[u]=num; num++;
G[num].v=u;
G[num].w=;
G[num].next=head[v];
head[v]=num;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int s=;
int t=n+;
init();
for(int i=;i<=m;i++){
int u,v,w1,w2;
scanf("%d%d%d%d",&u,&v,&w1,&w2);
in[i]=w1; //一开始这条边的流量
limit[v]+=w1; //v这个点的流入,增加
limit[u]-=w1; //u这个点的流出,减少
build(u,v,w2-w1); //剩下的可以操作的流量拿来建边
bh[i]=num; //标记这条边的反向边的num
}
int sout=;
for(int i=;i<=n;i++){
//如果流入比较大,那么从源点向其建边;流量就为这个点的流量;
if(limit[i]>) build(s,i,limit[i]),sout+=limit[i];
if(limit[i]<) build(i,t,-limit[i]);
//如果流出比较大,那么从这个点向汇点建边,流量为这个点的流量的绝对值;
}
int ans=dinic(s,t);
//只有当流入汇点的流量等于流入这个图的流量,才是有解;
if(ans==sout){
printf("YES\n");
for(int i=;i<=m;i++)
printf("%d\n",in[i]+G[bh[i]].w);
}
else printf("NO\n");
return ;
}