无源汇有上下界可行流:
问题:给定一张图,每条边流量必须在$[l,r]$之间,请你求出它的一个合法流。
考虑一种初始的想法:将每条边拆成两条,一条流量为$l$,一条流量为$r-l$。
Q:我们只考虑$r-l$的边建一个跟原图一样的图,跑一个合法流,最后加上所有的$l$不就行了吗?
A:显然不行,因为所有$l$并不相等,所以你加上$l$之后原本满足流量平衡的图就不满足流量平衡了。(而且无源汇无上下界跑出来是0啊)
但我们可以发现,相对于原图来说,所有必须流的流量$l$是凭空产生,而又凭空被吸收的。
而朴素的网络流问题也正是基于一个凭空产生流量的源点,一个凭空吸收流量的汇点来实现的。
于是处理流量平衡的问题我们可以这样做:
新建一个超级源向每个点连边,表示进入该点的流量至少是多少;新建一个超级汇,每个点向它连边,表示离开该点的流量至少是多少。
然后跑一个最大流,如果满流说明在流量平衡的条件下可以满足要求,否则说明不能满足要求。
更具体地,我们可以:
新建源点$S$,汇点$T$。
对于一条边$(u,v,l,r)$,我们连三条边:$(S,v,l)$,$(u,T,l)$,$(u,v,r-l)$。
边$(S,v,l)$表示进入$v$的流量至少要是$l$。
边$(u,T,l)$表示离开$u$的流量至少要是$l$。
边$(u,v,r-l)$表示流满$l$后的额外流量无下界,上界是$r-l$。
然后我们从$S$到$T$跑最大流。
跑完后检查这张图,当且仅当图中任意一点$u$均满足其与$S,T$的连边均满流时整张图存在合法流。
(若某一点不满流且整张图是最大流,那么无论如何调整一定会有一个点不满流,此时原图上该点的边一定存在流量小于$l$的情况)
(无须检查$>r$的条件,因为若$>r$不合法则一定是因为它造成了某个点$<l$才不合法的)
这个判断条件可以简化成最大流=$S$所有出边的流量之和=$T$所有入边的流量之和。
若存在合法流直接输出此时每条$(u,v,r-l)$边的流量$+l$即可。
#include<bits/stdc++.h> #define maxn 205 #define maxm 500005 #define inf 0x7fffffff #define ll long long #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; int hd[maxn],lim[maxm],to[maxm],nxt[maxm],fl[maxm]; int S,T,cnt=1,id[maxm],dis[maxn],inq[maxn]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline void addedge(int u,int v,int w){ to[++cnt]=v,fl[cnt]=w,nxt[cnt]=hd[u],hd[u]=cnt; to[++cnt]=u,fl[cnt]=0,nxt[cnt]=hd[v],hd[v]=cnt; } inline bool spfa(){ memset(dis,63,sizeof(dis)); memset(inq,0,sizeof(inq)); queue<int> q;q.push(S); dis[S]=0,inq[S]=1; while(!q.empty()){ int u=q.front(); q.pop(),inq[u]=0; for(int i=hd[u];i;i=nxt[i]){ int v=to[i],w=fl[i]; if(w>0&&dis[v]>dis[u]+1){ dis[v]=dis[u]+1; if(!inq[v]) q.push(v),inq[v]=1; } } } return dis[T]!=dis[T+1]; } inline int dfs(int u,int flow){ int sum=0; if(u==T) return flow; for(int i=hd[u];i;i=nxt[i]){ int v=to[i]; if(dis[v]==dis[u]+1 && fl[i]>0){ int f=dfs(v,min(flow,fl[i])); sum+=f,fl[i]-=f,fl[i^1]+=f,flow-=f; } if(!flow) break; } if(sum==0) dis[u]=-1; return sum; } inline int Dinic(){int ans=0;while(spfa())ans+=dfs(S,inf);return ans;} int main(){ int n=read(),m=read();S=0,T=n+1; for(int i=1;i<=m;i++){ int u=read(),v=read(); lim[i]=read(); int r=read(); addedge(S,v,lim[i]),addedge(u,T,lim[i]); addedge(u,v,r-lim[i]),id[i]=cnt; } Dinic(); int flag=1; for(int i=hd[S];i;i=nxt[i]) if(fl[i]!=0) flag=0; for(int i=hd[T];i;i=nxt[i]) if(fl[i^1]!=0) flag=0; if(!flag) printf("NO "); else{ printf("YES "); for(int i=1;i<=m;i++) printf("%d ",fl[id[i]]+lim[i]); } return 0; }
有源汇有上下界最大流:
当这张图给定源点$S'$,汇点$T'$的时候,它不再满足流量平衡,我们好像没法处理了。
然而根据化归思想,我们从$T'$往$S'$连一条$(0,inf)$的边,这张图就满足流量平衡了……
但我们按上面的方法跑出来不一定是原图的最大流,怎么办呢?
考虑一个想法:在残量网络上再跑一遍$S'$到$T'$的最大流,如何?
注意到跑出来的残量网络在新图上一定是流量平衡的,那么我们跑一遍最大流不会影响除了$S',T'$之外所有点的流量平衡。
而且,因为我们只会沿着原图上的边跑而不会走到$S$或$T$,所以最后跑出来的流一定还是合法的(根本没影响合法的判定条件)。
于是跑两遍最大流就可以解决这个问题了。
第二遍跑的时候不用考虑第一遍的答案,因为第一遍跑完$S'$点流量平衡,相当于初始流量为0。
#include<bits/stdc++.h> #define maxn 205 #define maxm 500005 #define inf 0x7fffffff #define ll long long #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; int hd[maxn],lim[maxm],to[maxm],nxt[maxm],fl[maxm]; int S,T,cnt=1,id[maxm],dis[maxn],inq[maxn]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline void addedge(int u,int v,int w){ to[++cnt]=v,fl[cnt]=w,nxt[cnt]=hd[u],hd[u]=cnt; to[++cnt]=u,fl[cnt]=0,nxt[cnt]=hd[v],hd[v]=cnt; } inline bool spfa(){ memset(dis,63,sizeof(dis)); memset(inq,0,sizeof(inq)); queue<int> q;q.push(S); dis[S]=0,inq[S]=1; while(!q.empty()){ int u=q.front(); q.pop(),inq[u]=0; for(int i=hd[u];i;i=nxt[i]){ int v=to[i],w=fl[i]; if(w>0&&dis[v]>dis[u]+1){ dis[v]=dis[u]+1; if(!inq[v]) q.push(v),inq[v]=1; } } } return dis[T]!=dis[T+1]; } inline int dfs(int u,int flow){ int sum=0; if(u==T) return flow; for(int i=hd[u];i;i=nxt[i]){ int v=to[i]; if(dis[v]==dis[u]+1 && fl[i]>0){ int f=dfs(v,min(flow,fl[i])); sum+=f,fl[i]-=f,fl[i^1]+=f,flow-=f; } if(!flow) break; } if(sum==0) dis[u]=-1; return sum; } inline int Dinic(){int ans=0;while(spfa())ans+=dfs(S,inf);return ans;} int main(){ int n=read(),m=read(),s=read(),t=read(); S=0,T=n+1; int sum=0,u,v,r; for(int i=1;i<=m+1;i++){ if(i==m+1) u=t,v=s,lim[i]=0,r=inf; else u=read(),v=read(),lim[i]=read(),r=read(); addedge(S,v,lim[i]),addedge(u,T,lim[i]),addedge(u,v,r-lim[i]),id[i]=cnt,sum+=lim[i]; } int mx=Dinic(); if(sum!=mx) printf("please go home to sleep "); else S=s,T=t,printf("%d ",Dinic()); return 0; }