上下界网络流
其实之前也写过博客介绍过的,但还没有完整地讲过一遍,这里就来总结一下。
无源汇有上下界可行流
针对任何的有上下界的网络流的边,我们考虑将它转化一下,将流量为
l
i
/
r
i
l_{i}/r_{i}
li/ri的,边拆成
l
i
l_{i}
li与
r
i
−
l
i
r_{i}-l_{i}
ri−li。
对于流量为
l
i
l_{i}
li的边,我们称其为必要边,即这条边必须流满,而另一条流量为
r
i
−
l
i
r_{i}-l_{i}
ri−li的边,就是不需要流满的非必要边。
我们考虑如何让必要边一定流满。
我们可以新建一个超级源点
S
S
S与一个超级汇点
T
T
T。我们将
u
u
u连向
T
T
T,
S
S
S连向
v
v
v,它们的流量都是
l
i
l_{i}
li。
这样的话,当原图是合法时并没有改变
u
u
u与
v
v
v两个点自己的经过流量,只会影响我们新建的
S
S
S与
T
T
T。
当将所有的边都进行这样的转化后,我们只需要跑一边dinic,看原点的流量流满没有即可。
至于每条非必需边的流量,只需要看它的非必需边在这个过程中减少了多少即可。
源码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 205
#define MAXM 30005
#define reg register
typedef long long LL;
typedef pair<int,LL> pii;
const int INF=0x7f7f7f7f;
const LL inf=0x7f7f7f7f7f7f;
template<typename _T>
inline void read(_T &x){
_T f=1;x=0;char s=getchar();
while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
int n,m,val[MAXN],head[MAXN],tot,S,T,cnt,sum,dep[MAXN],id[MAXM],dy[MAXM];
queue<int> q;
struct edge{int to,nxt,flow,op;}e[MAXM];
void addEdge(int u,int v,int w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
void addedge(int u,int v,int w){addEdge(u,v,w);e[tot].op=tot+1;addEdge(v,u,0);e[tot].op=tot-1;}
bool bfs(){
while(!q.empty())q.pop();
for(int i=1;i<=cnt;i++)dep[i]=0;q.push(S);dep[S]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!dep[v]&&e[i].flow){
q.push(v);dep[v]=dep[u]+1;
if(v==T)return 1;
}
}
}
return 0;
}
int dosaka(int u,int maxf){
if(u==T||!maxf)return maxf;int res=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;if(!e[i].flow||dep[v]!=dep[u]+1)continue;
int f=dosaka(v,min(e[i].flow,maxf));
e[i].flow-=f;e[e[i].op].flow+=f;res+=f;maxf-=f;
}
return res;
}
LL sakura(){LL res=0;while(bfs())res+=1ll*dosaka(S,INF);return res;}
signed main(){
read(n);read(m);S=n+1;cnt=T=n+2;
for(int i=1;i<=m;i++){
int s,t,low,up;read(s);read(t);read(low);read(up);
dy[i]=low;addedge(s,t,up-low);id[i]=tot;val[s]-=low;val[t]+=low;
}
for(int i=1;i<=n;i++){
if(val[i]<0)addedge(i,T,-val[i]);
if(val[i]>0)addedge(S,i,val[i]),sum+=val[i];
}
int ans=sakura();if(ans==sum)puts("YES");else{puts("NO");return 0;}
for(int i=1;i<=m;i++)printf("%d\n",e[id[i]].flow+dy[i]);
return 0;
}
有源汇有上下界最大流
其实这道题的模型是与上一道题差不多的。
我们先需要判断这个图是否可行,还是向上题一样建图跑网络流,唯一不同的是需要在原来的汇点
t
t
t和原点
s
s
s之间连一条流量为inf的边,再跑网络流来判断。
因为此时的流量是守恒的,这条边的流量就等于可行流的流量。
如果要求出它的最大流的话,就只需要将源点汇点改为原来的 s s s与 t t t,再跑一道最大流即可。
源码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 305
#define MAXM 40005
#define reg register
typedef long long LL;
typedef pair<int,LL> pii;
const int INF=0x7f7f7f7f;
const LL inf=0x7f7f7f7f7f7f;
template<typename _T>
inline void read(_T &x){
_T f=1;x=0;char s=getchar();
while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
int n,m,val[MAXN],head[MAXN],tot,s,t,S,T,cnt,sum,dep[MAXN],cur[MAXN];
queue<int> q;
struct edge{int to,nxt,flow,op;}e[MAXM];
struct ming{int u,v,up,down;}a[MAXM];
void addEdge(int u,int v,int w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
void addedge(int u,int v,int w){addEdge(u,v,w);e[tot].op=tot+1;addEdge(v,u,0);e[tot].op=tot-1;}
bool bfs(){
for(int i=1;i<=cnt;i++)dep[i]=0,cur[i]=head[i];
while(!q.empty())q.pop();q.push(S);dep[S]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!dep[v]&&e[i].flow){
q.push(v);dep[v]=dep[u]+1;
if(v==T)return 1;
}
}
}
return 0;
}
int dosaka(int u,int maxf){
if(u==T||!maxf)return maxf;int res=0;
for(int i=cur[u];i;i=e[i].nxt){
cur[u]=i;int v=e[i].to;if(!e[i].flow||dep[v]!=dep[u]+1)continue;
int f=dosaka(v,min(e[i].flow,maxf));
e[i].flow-=f;e[e[i].op].flow+=f;res+=f;maxf-=f;
}
return res;
}
LL sakura(){LL res=0;while(bfs())res+=1ll*dosaka(S,INF);return res;}
signed main(){
read(n);read(m);read(s);read(t);S=n+1;cnt=T=n+2;
for(int i=1;i<=m;i++){
read(a[i].u);read(a[i].v);read(a[i].down);read(a[i].up);
addedge(a[i].u,a[i].v,a[i].up-a[i].down);
val[a[i].u]-=a[i].down;val[a[i].v]+=a[i].down;
}
for(int i=1;i<=n;i++){
if(val[i]<0)addedge(i,T,-val[i]);
if(val[i]>0)addedge(S,i,val[i]),sum+=val[i];
}
addedge(t,s,INF);LL ans=sakura();
if(ans<sum){puts("please go home to sleep");return 0;}
S=s;T=t;ans=sakura();printf("%lld\n",ans);
return 0;
}
有源汇有上下界最小流
最小流的话判断过程是与上面的是一样的,唯一不同的就是最小流的求法。
很荣容易发现,再最开始跑dinic来判断的过程中,
S
S
S到
T
T
T的过程中已经将
s
s
s到
t
t
t的路径上的非必要边都扩展了。
所以我们只需要将
t
t
t到
s
s
s的无限流量的边的流量记录下来,减去删去这条边后由
t
t
t到
s
s
s的最大流即可。
源码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 1000100
#define reg register
typedef long long LL;
typedef pair<int,LL> pii;
const int INF=0x7f7f7f7f;
template<typename _T>
inline void read(_T &x){
_T f=1;x=0;char s=getchar();
while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
int n,m,val[MAXN],head[MAXN],tot,s,t,S,T,cnt,sum,dep[MAXN],cur[MAXN];
queue<int> q;
struct edge{int to,nxt,flow,op;}e[MAXN];
struct ming{int u,v,up,down;}a[MAXN];
inline void addEdge(int u,int v,int w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
inline void addedge(int u,int v,int w){addEdge(u,v,w);e[tot].op=tot+1;addEdge(v,u,0);e[tot].op=tot-1;}
inline bool bfs(){
for(reg int i=1;i<=cnt;++i)dep[i]=0,cur[i]=head[i];
while(!q.empty())q.pop();q.push(S);dep[S]=1;
while(!q.empty()){
const int u=q.front();q.pop();
for(reg int i=head[u];i;i=e[i].nxt){
const int v=e[i].to;
if(!dep[v]&&e[i].flow)
q.push(v),dep[v]=dep[u]+1;
}
}
return (dep[T]>0);
}
int dosaka(const int u,int maxf){
if(u==T||!maxf)return maxf;int res=0;
for(reg int &i=cur[u];i;i=e[i].nxt){
int v=e[i].to;if(!e[i].flow||dep[v]!=dep[u]+1)continue;
int f=dosaka(v,min(e[i].flow,maxf));
e[i].flow-=f;e[e[i].op].flow+=f;
res+=f;maxf-=f;if(!maxf)break;
}
return res;
}
int sakura(){int res=0;while(bfs())res+=1ll*dosaka(S,INF);return res;}
signed main(){
read(n);read(m);read(s);read(t);S=n+1;cnt=T=n+2;
for(reg int i=1;i<=m;++i){
read(a[i].u);read(a[i].v);read(a[i].down);read(a[i].up);
addedge(a[i].u,a[i].v,a[i].up-a[i].down);
val[a[i].u]-=a[i].down;val[a[i].v]+=a[i].down;
}
for(reg int i=1;i<=n;++i){
if(val[i]<0)addEdge(i,T,-val[i]);
if(val[i]>0)addEdge(S,i,val[i]),sum+=val[i];
}
addedge(t,s,INF);int tmp=sakura(),ans=e[tot].flow;e[tot-1].flow=e[tot].flow=0;
for(int i=head[S];i;i=e[i].nxt)if(e[i].flow){puts("please go home to sleep");return 0;}
S=t;T=s;ans=ans-sakura();printf("%lld\n",ans);
return 0;
}
LOJ上恰好这三道题都有,就先将这三道题的链接与代码贴出来了。