上下界网络流
本篇笔记写于远古时代,写时虽然参考了一些的资料(罗列在文末),但口胡仍然占多数,
准确性堪忧,如有不准确的地方,欢迎指正。
无源汇有上下界可行流
首先要满足流量下界的需求,强行让每条边都流过low的流量,那么每条边只剩下的容量为upp-low,同时设流入点x的流量-流出点x的流量为d[x]。
考虑调整流网络使得流量平衡(让d[x]变为0)1)对于d[x]<0的点,我们希望向x流入-d[x]的流量 2)对于d[x]>0的点,我们希望从x流出d[x]的流量;
因此,考虑新建附源汇点S-T,S指向所有d[x]>0的x,所有d[x]>0的x指向T就能提供像这样调整的“势”。
对流网络跑最大流,如果存在边(S,?,k)或(?,T,k)满足k!=0,则可知无法调整到流量平衡,即不存在可行流。 因为sum a[x] (a[x]>0)=sum -a[x] (a[x]<0),具体操作时,只检差一边就行了。
显然,此时的一条边的可行流量为跑完最大流后所减少的流量(即反边流量)+原来的下界。
#include <bits/stdc++.h>
using namespace std;
const int N=207;
const int L=30407;
const int inf=0x3f3f3f3f;
int S,T;
int head[N],to[L],cap[L],last[L],cnt=1;
int que[N],lev[N],hd,tl;
inline void add_edge(int x,int y,int c1,int c2=0) {
to[++cnt]=y,cap[cnt]=c1,last[cnt]=head[x],head[x]=cnt;
to[++cnt]=x,cap[cnt]=c2,last[cnt]=head[y],head[y]=cnt;
}
inline bool bfs() {
memset(lev,0,sizeof lev);
lev[S]=1;
que[hd=0,tl=1]=S;
while(hd<tl) {
int x=que[++hd];
for(int i=head[x]; i; i=last[i]) if(cap[i]>0 && !lev[to[i]])
lev[to[i]]=lev[x]+1, que[++tl]=to[i];
}
return lev[T]!=0;
}
int dfs(int x,int tf) {
if(x==T) return tf;
int tot=0,tmp;
for(int i=head[x]; i; i=last[i]) if(cap[i]>0 && lev[x]+1==lev[to[i]]) {
tmp=dfs(to[i],min(tf-tot,cap[i]));
if(tmp) cap[i]-=tmp,cap[i^1]+=tmp,tot+=tmp;
if(tot==tf) break;
}
if(!tot) lev[x]=-1;
return tot;
}
int n,m,a[N],low[L];
bool check() {
for(int i=head[S]; i; i=last[i])
if(cap[i]) return false;
for(int i=head[T]; i; i=last[i])
if(cap[i^1]) return false;
return true;
}
void solve() {
cnt=1;
memset(a,0,sizeof a);
memset(head,0,sizeof head);
for(int i=1,x,y,upp; i<=m; ++i) {
scanf("%d%d%d%d",&x,&y,&low[i],&upp);
a[x]-=low[i];
a[y]+=low[i];
add_edge(x,y,upp-low[i]);
}
int lst=cnt;
S=n+1, T=n+2;
for(int i=1; i<=n; ++i) {
if(a[i]>0) add_edge(S,i,a[i]);
if(a[i]<0) add_edge(i,T,-a[i]);
}
while(bfs()) dfs(S,inf);
if(!check()) {
puts("NO");
return;
}
puts("YES");
for(int i=2; i<=lst; i+=2) {
printf("%d\n",cap[i^1]+low[i>>1]);
}
}
int main() {
while(~scanf("%d%d",&n,&m)) solve();
return 0;
}
有源汇有上下界可行流
无源汇的流网络所有点都要满足d[x]=0,这说明它其实是一个“流循环图”。而对于有源汇的合法的流网络,除了d[s]<0,d[t]>0外,其余点满足d[x]=0,并且d[s]=-d[t],这启发我们加入t到s的一条下界为0,上界为inf的边,这样,图转化为一个“流循环图”,可以借助无源汇的算法调整。
来看下调整后流网络的边t-s,它的反边流量k=与s、t有关的基准流量,即这样的一组调整必然会给s-t的最大流带来k的贡献。答案即为k。
#include <bits/stdc++.h>
using namespace std;
const int N=207;
const int L=30407;
const int inf=0x3f3f3f3f;
int S,T,s,t;
int head[N],to[L],cap[L],last[L],cnt=1;
int que[N],lev[N],hd,tl;
inline void add_edge(int x,int y,int c1,int c2=0) {
to[++cnt]=y,cap[cnt]=c1,last[cnt]=head[x],head[x]=cnt;
to[++cnt]=x,cap[cnt]=c2,last[cnt]=head[y],head[y]=cnt;
}
inline bool bfs() {
memset(lev,0,sizeof lev);
lev[S]=1;
que[hd=0,tl=1]=S;
while(hd<tl) {
int x=que[++hd];
for(int i=head[x]; i; i=last[i]) if(cap[i]>0 && !lev[to[i]])
lev[to[i]]=lev[x]+1, que[++tl]=to[i];
}
return lev[T]!=0;
}
int dfs(int x,int tf) {
if(x==T) return tf;
int tot=0,tmp;
for(int i=head[x]; i; i=last[i]) if(cap[i]>0 && lev[x]+1==lev[to[i]]) {
tmp=dfs(to[i],min(tf-tot,cap[i]));
if(tmp) cap[i]-=tmp,cap[i^1]+=tmp,tot+=tmp;
if(tot==tf) break;
}
if(!tot) lev[x]=-1;
return tot;
}
int n,m,a[N],low[L];
bool check() {
for(int i=head[S]; i; i=last[i])
if(cap[i]) return false;
for(int i=head[T]; i; i=last[i])
if(cap[i^1]) return false;
return true;
}
void solve() {
cnt=1;
memset(a,0,sizeof a);
memset(head,0,sizeof head);
for(int i=1,x,y,upp; i<=m; ++i) {
scanf("%d%d%d%d",&x,&y,&low[i],&upp);
a[x]-=low[i];
a[y]+=low[i];
add_edge(x,y,upp-low[i]);
}
add_edge(t,s,inf);
S=n+1, T=n+2;
for(int i=1; i<=n; ++i) {
if(a[i]>0) add_edge(S,i,a[i]);
if(a[i]<0) add_edge(i,T,-a[i]);
}
while(bfs()) dfs(S,inf);
if(!check()) {
puts("please go home to sleep");
return;
}
printf("%d\n",upp[head[s]]);
}
int main() {
while(~scanf("%d%d%d%d",&n,&m,&s,&t))
solve();
return 0;
}
有源汇有上下界最大流
接着第二来讲,因此,再将此边即所有与ST关联的边删除,剩下的便是张一般的流网络,k+此时s-t的最大流即为答案。
进一步的,我们可以不必删除t-s边和ST关联的边,直接采用当前流网络的s-t的最大流作为答案。因为1)t-s边的反边的基准流量必然会被增广;2)不可能存在经过S或T的增广路(S的出边、T的入边全部断开)。
#include <bits/stdc++.h>
using namespace std;
const int N=207;
const int L=30407;
const int inf=0x3f3f3f3f;
int S,T,s,t;
int head[N],to[L],cap[L],last[L],cnt=1;
int que[N],lev[N],hd,tl;
inline void add_edge(int x,int y,int c1,int c2=0) {
to[++cnt]=y,cap[cnt]=c1,last[cnt]=head[x],head[x]=cnt;
to[++cnt]=x,cap[cnt]=c2,last[cnt]=head[y],head[y]=cnt;
}
inline bool bfs() {
memset(lev,0,sizeof lev);
lev[S]=1;
que[hd=0,tl=1]=S;
while(hd<tl) {
int x=que[++hd];
for(int i=head[x]; i; i=last[i]) if(cap[i]>0 && !lev[to[i]])
lev[to[i]]=lev[x]+1, que[++tl]=to[i];
}
return lev[T]!=0;
}
int dfs(int x,int tf) {
if(x==T) return tf;
int tot=0,tmp;
for(int i=head[x]; i; i=last[i]) if(cap[i]>0 && lev[x]+1==lev[to[i]]) {
tmp=dfs(to[i],min(tf-tot,cap[i]));
if(tmp) cap[i]-=tmp,cap[i^1]+=tmp,tot+=tmp;
if(tot==tf) break;
}
if(!tot) lev[x]=-1;
return tot;
}
int n,m,a[N],low[L];
bool check() {
for(int i=head[S]; i; i=last[i])
if(cap[i]) return false;
for(int i=head[T]; i; i=last[i])
if(cap[i^1]) return false;
return true;
}
void solve() {
cnt=1;
memset(a,0,sizeof a);
memset(head,0,sizeof head);
for(int i=1,x,y,upp; i<=m; ++i) {
scanf("%d%d%d%d",&x,&y,&low[i],&upp);
a[x]-=low[i];
a[y]+=low[i];
add_edge(x,y,upp-low[i]);
}
add_edge(t,s,inf);
S=n+1, T=n+2;
for(int i=1; i<=n; ++i) {
if(a[i]>0) add_edge(S,i,a[i]);
if(a[i]<0) add_edge(i,T,-a[i]);
}
while(bfs()) dfs(S,inf);
if(!check()) {
puts("please go home to sleep");
return;
}
int ret=0;
S=s, T=t;
while(bfs()) ret+=dfs(S,inf);
printf("%d\n",ret);
}
int main() {
while(~scanf("%d%d%d%d",&n,&m,&s,&t))
solve();
return 0;
}
有源汇有上下界最小流
直接的做法是s-t的最小流=基准流量k - (t-s的最大流)。此时要注意删除t-s的边(若不删除,则会使得t-s的最大流接近无穷大,成功GG)。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=5e4+20;
const int L=2e6+20;
const LL inf=1e15;
int n,m,s,t,d[N];
int S=N-1,T=N-2;
int head[N],cur[N],to[L],last[L],cnt=1;
int que[N],lev[N],hd,tl;
LL upp[L];
inline void add_edge(int x,int y,LL u) {
to[++cnt]=y,upp[cnt]=u,last[cnt]=head[x],head[x]=cnt;
to[++cnt]=x,upp[cnt]=0,last[cnt]=head[y],head[y]=cnt;
}
inline bool bfs() {
memset(lev,0,(n+2)<<2);
lev[S]=1;
que[hd=0,tl=1]=S;
while(hd<tl) {
int x=que[++hd];
for(int i=head[x]; i; i=last[i]) if(upp[i]>0 && !lev[to[i]])
lev[to[i]]=lev[x]+1, que[++tl]=to[i];
}
return lev[T]!=0;
}
LL dfs(int x,LL tf) {
if(x==T) return tf;
LL tmp;
for(int &i=cur[x]; i; i=last[i]) if(upp[i]>0 && lev[x]+1==lev[to[i]]) {
tmp=dfs(to[i],min(tf,upp[i]));
if(tmp) {
upp[i]-=tmp;
upp[i^1]+=tmp;
return tmp;
}
}
lev[x]=-1;
return 0;
}
template<typename T> inline void read(T&d) {
register char ch=getchar(); d=0;
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) d=d*10+ch-'0',ch=getchar();
}
LL dinic() {
LL ret=0,tmp;
while(bfs()) {
for(int i=0; i<=n+1; ++i) cur[i]=head[i];
while((tmp=dfs(S,inf))>0) ret+=tmp;
}
return ret;
}
int main() {
read(n);read(m);read(s);read(t);
for(int i=1,x,y,low,upp; i<=m; ++i) {
read(x);read(y);read(low);read(upp);
d[x]-=low;
d[y]+=low;
add_edge(x,y,upp-low);
}
S=0, T=n+1;
add_edge(t,s,inf);
for(int i=1; i<=n; ++i) {
if(d[i]>0) add_edge(S,i,d[i]);
if(d[i]<0) add_edge(i,T,-d[i]);
}
dinic();
for(int i=head[S]; i; i=last[i]) {
if(upp[i]) {
puts("please go home to sleep");
return 0;
}
}
long long st=upp[head[s]];
upp[head[s]]=0;
upp[head[t]]=0;
S=t, T=s;
printf("%lld\n",st-dinic());
return 0;
}
无源汇有上下界最小费用可行流
干掉下界的时候,把费用先计算上。把超级源超级汇求最大流的一步,换成最小费用最大流即可。即满足合法的前提下,最小化费用。费用就是之前的费用加上这次的费用。
有源汇有上下界最小费用可行流
参考第一到第二的转化,添加边t-s,下界为0,上界为inf,费用为0。然后按照第五进行调整至所有点流量平衡即可。
有源汇有上下界最小费用最大流
似乎直接按照第三改一波。。mf改为mcmf。
有源汇有上下界最小费用最小流
似乎直接按照第四改一波。。我也不清楚,因为要减去最多的花费 最大费用流???
参考资料:
学习笔记 上下界网络流 作者*Miracle*。
网络流常见建模总结 作者panda_2134。