2022.02.24 網路流複習
2022.02.24 網路流複習
1. 有上下界網路流
1. 無源匯有上下界可行流
https://blog.csdn.net/wu_tongtong/article/details/73320968
1.1 建圖
無源匯有上下界可行流(也就是迴圈流)
模型:一個網路,求出一個流,使得每條邊的流量必須>=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的最大流即可
第一步,令每條邊流量為流量下限,建出這個圖的殘量網路,每條邊的流量=流量上限-流量下限;
第二步,求出原圖不滿足流量守恆的附加流,附加流與原圖合併後流量守恆。設\(A_i\)表示原圖中流入流量-流出流量,那麼對於現在的圖來說:
- \(A_i>0\),附加流流入流量小於流出流量;
- \(A_i<0\),附加流流入流量大於流出流量;
簡而言之,\(A_i\)=附加流流出流量-流入流量。
第三步,對於沒有來源或者去處的流量,我們建一個全新的源點Si和全新的匯點Ti來為這些流量提供一個溫暖的家:
- 對於\(A_i>0\),從Si到i建一條容量為\(|A_i|\)的邊;
- 對於\(A_i<0\)
然後從Si到Ti跑Dinic或Ek(菜菜的eleveni其他演算法學得並不好,只有Dinic與Ek學得十分熟練)。
1.2 對於1.1的一些說明
1.2.1 指向Ti的流量上限之和=從Si流出的流量上限之和
一條邊對於起點和終點流量的影響是大小相等,正負相反的,所以整個圖的\(A_i\)之和為0.
1.2.2 為什麼跑最大流
因為如果想要求到想要的附加流,第三步中建的邊必須滿流(廢話,要不然空穴來網路流?必須先把缺失的流量補全才能繼續在上面進行騷操作),那麼就跑最大流。
如果最大流不等於流出Si的流量之和,說明這個圖沒有可行流,孩紙,洗洗睡吧;
如果最大流等於流出Si的流量之和,說明已經找到了可行流,每條邊可行流流量=原圖中的流量下限+附加流中的流量(跑完Dinic後,新圖中反向邊的權值),換而言之就是新圖與原圖合起來流量平衡,現在新圖正向邊流量平衡,說明新圖反向邊與原圖流量平衡;
如果最大流大於……沒有大於,要不然就又是空穴來流量了!
2. 有源匯有上下界可行流
https://www.luogu.com.cn/blog/xiaoziyaoxzy/solution-p5192
2.1 有源匯有上下界可行流
既然咱會無源匯有上下界可行流,那就可以把有源匯有上下界可行流變成無源匯有上下界可行流。
最簡單的方法是:從T到S連一條容量為inf的邊,讓流量迴圈一下,這就不平衡了。
接下來跑無源匯有上下界可行流模板就好了。
2.2 有源匯有上下界最大流
咱根據有源匯有上下界可行流的想法,先跑出來可行流flow1,此時在新圖上已經找不出來從Si到Ti增廣路了,但是在原圖中從S到T可能會存在增廣路。
所以我們要刪掉從T到S容量為inf的邊,不然永遠都是一個迴圈,跑不出去。然後繼續跑從S到T的Dinic,得出舊圖上的最大流為flow2,總的最大流就是flow1+flow2。
2.3 有源匯有上下界最小流
老規矩,先算出從Si到Ti的最大流flow1。
根據有源匯有上下界最大流的想法,咱可以先刪去從T到S容量為inf的邊,然後從T到S跑一個最大流,算出需要退回去的最大流量為flow2,說明從S到T剩下的最小流就是flow1-flow2。
3. 練習題
PS:洛谷上只有三道題,所以有些題來自ZOJ,這些題會在題號前面加上“ZOJ”這三個字元。
3.1 ZOJ2314 Reactor Cooling(無源匯有上下界網路流模板)
https://zoj.pintia.cn/problem-sets/91827364500/problems/91827365813
沒啥可說的,畢竟是模板。非要說啥,那就是visual記得清零!
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int M=1e5+10;
int n,m,cnt=1,preflow[N],stain[N],head[N],dep[N],cur[N],visual;
int S,T,id[N],Ttot;
struct node{
int to,next,val;
}a[M];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
inline void addi(int u,int v,int w){
++cnt;
a[cnt].to=v;
a[cnt].val=w;
a[cnt].next=head[u];
head[u]=cnt;
}
inline void add(int u,int v,int w){
addi(u,v,w);addi(v,u,0);
}
inline int bfs(int s,int t){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof(head));
queue<int>q;
dep[s]=1;q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=a[i].next){
int v=a[i].to;
if(a[i].val&&!dep[v])dep[v]=dep[x]+1,q.push(v);
}
}
return dep[t];
}
inline int dfs(int x,int t,int f){
if(t==x)return f;
int ans=0,fi=0;
for(int i=cur[x];i&&f>ans;i=a[i].next){
int v=a[i].to;cur[x]=i;
if(a[i].val&&dep[v]==dep[x]+1&&(fi=dfs(v,t,min(f-ans,a[i].val)))>0)
a[i].val-=fi,a[i^1].val+=fi,ans+=fi;
}
return ans;
}
inline int Dinic(int s,int t){
int flow=0;
while(bfs(s,t)){
int x=0;
if((x=dfs(s,t,1<<30))>0)flow+=x;
}
return flow;
}
inline bool check(){
for(int i=head[S];i;i=a[i].next)if(a[i].val)return false;
return true;
}
signed main(){
cin>>Ttot;
while(Ttot--){
memset(stain,0,sizeof(stain));
memset(preflow,0,sizeof(preflow));
memset(head,0,sizeof(head));
memset(a,0,sizeof(a));
memset(id,0,sizeof(id));
cnt=1;visual=0;
cin>>n>>m;
S=n+1,T=S+1;
for(int i=1;i<=m;i++){
int u,v,w,x;
u=read();v=read();w=read();x=read();
preflow[i]=w;
stain[u]-=w;stain[v]+=w;
add(u,v,x-w);
id[i]=cnt;
}
for(int i=1;i<=n;i++)
if(stain[i]>0)add(S,i,stain[i]),visual+=stain[i];
else if(stain[i]<0)add(i,T,-stain[i]);
int flow=Dinic(S,T);
if(flow==visual){
puts("YES");
for(int i=1;i<=m;i++)cout<<preflow[i]+a[id[i]].val<<endl;
}else puts("NO");
}
return 0;
}
3.2 P5192 Zoj3229 Shoot the Bullet|東方文花帖|(有源匯有上下界最大流模板)
https://www.luogu.com.cn/problem/P5192
溫馨提示:不要嘗試作死,比如想試試不刪掉從T到S容量為inf的邊會發生什麼,有的時候,你機房的電腦可能會抗議!
洛谷:只需要輸出總照片數
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+10;
const int M=1e6+10;
const int maxn=1e7;
int n,m,cnt=1,preflow[M],stain[N],head[N],dep[N],cur[N],visual;
int S,T,Si,Ti,id[400][1010],girl[1010],fin[400][1010];
struct node{
int to,next,val;
}a[M];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
inline void addi(int u,int v,int w){
++cnt;
a[cnt].to=v;
a[cnt].val=w;
a[cnt].next=head[u];
head[u]=cnt;
}
inline void add(int u,int v,int w){
addi(u,v,w);addi(v,u,0);
}
inline int bfs(int s,int t){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof(head));
queue<int>q;
dep[s]=1;q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=a[i].next){
int v=a[i].to;
if(a[i].val&&!dep[v])dep[v]=dep[x]+1,q.push(v);
}
}
return dep[t];
}
inline int dfs(int x,int t,int f){
if(t==x)return f;
int ans=0,fi=0;
for(int i=cur[x];i&&f>ans;i=a[i].next){
int v=a[i].to;cur[x]=i;
if(a[i].val&&dep[v]==dep[x]+1&&(fi=dfs(v,t,min(f-ans,a[i].val)))>0)
a[i].val-=fi,a[i^1].val+=fi,ans+=fi;
}
return ans;
}
inline int Dinic(int s,int t){
int flow=0;
while(bfs(s,t)){
int x=0;
if((x=dfs(s,t,1<<30))>0)flow+=x;
}
return flow;
}
signed main(){
while(~scanf("%d%d",&n,&m)){
memset(head,0,sizeof(head));
memset(a,0,sizeof(a));
memset(id,0,sizeof(id));
memset(stain,0,sizeof(stain));
memset(girl,0,sizeof(girl));
memset(preflow,0,sizeof(preflow));
visual=0,cnt=1;
S=n+m+1,T=S+1;Si=T+1,Ti=Si+1;
for(int i=1;i<=m;i++){
girl[i]=read();
stain[T]+=girl[i];stain[n+i]-=girl[i];
add(n+i,T,maxn-girl[i]);//old graph
}
for(int i=1;i<=n;i++){
int C,D;
C=read();D=read();
id[i][0]=C;
add(S,i,D);//old graph
for(int j=1;j<=C;j++){
int u,v,w;
u=read();v=read();w=read();
++u;
stain[n+u]+=v;stain[i]-=v;
add(i,n+u,w-v);//new graph and old graph
id[i][j]=u;
}
}
for(int i=1;i<=n+m+2;i++)
if(stain[i]>0)add(Si,i,stain[i]),visual+=stain[i];
else if(stain[i]<0)add(i,Ti,-stain[i]);
add(T,S,maxn);
//cout<<"first dinic "<<endl;
int flow=Dinic(Si,Ti);
//cout<<flow<<endl;
if(flow!=visual)puts("-1\n");
else{
a[cnt].val=0;a[cnt^1].val=0;
flow+=Dinic(S,T);
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=a[j].next)if(j%2==0){
int v=a[j].to;
fin[i][v-n]=a[j^1].val;
}
cout<<flow<<endl<<endl;
/*for(int i=1;i<=n;i++){
for(int j=1;j<=id[i][0];j++)
cout<<fin[i][id[i][j]]<<endl;
}*/
}
}
return 0;
}
ZOJ:輸出每天每個姑娘的照片數的時候記得加上流量下屆!
為什麼這兩個程式碼長得差不多呢?因為我原本想直接寫ZOJ這道題,結果沒加上流量下屆,寫掛了,於是直接先交了洛谷的,拐回來繼續修ZOJ的。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+10;
const int M=1e6+10;
const int maxn=1e7;
int n,m,cnt=1,preflow[M],stain[N],head[N],dep[N],cur[N],visual;
int S,T,Si,Ti,id[400][1010],girl[1010],fin[400][1010],low[400][1010];
struct node{
int to,next,val;
}a[M<<1];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
inline void addi(int u,int v,int w){
++cnt;
a[cnt].to=v;
a[cnt].val=w;
a[cnt].next=head[u];
head[u]=cnt;
}
inline void add(int u,int v,int w){
addi(u,v,w);addi(v,u,0);
}
inline int bfs(int s,int t){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof(head));
queue<int>q;
dep[s]=1;q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=a[i].next){
int v=a[i].to;
if(a[i].val&&!dep[v])dep[v]=dep[x]+1,q.push(v);
}
}
return dep[t];
}
inline int dfs(int x,int t,int f){
if(t==x)return f;
int ans=0,fi=0;
for(int i=cur[x];i&&f>ans;i=a[i].next){
int v=a[i].to;cur[x]=i;
if(a[i].val&&dep[v]==dep[x]+1&&(fi=dfs(v,t,min(f-ans,a[i].val)))>0)
a[i].val-=fi,a[i^1].val+=fi,ans+=fi;
}
return ans;
}
inline int Dinic(int s,int t){
int flow=0;
while(bfs(s,t)){
int x=0;
if((x=dfs(s,t,1<<30))>0)flow+=x;
}
return flow;
}
signed main(){
while(~scanf("%d%d",&n,&m)){
memset(head,0,sizeof(head));
memset(a,0,sizeof(a));
memset(id,0,sizeof(id));
memset(stain,0,sizeof(stain));
memset(girl,0,sizeof(girl));
memset(preflow,0,sizeof(preflow));
memset(low,0,sizeof(low));
memset(fin,0,sizeof(fin));
visual=0,cnt=1;
S=n+m+1,T=S+1;Si=T+1,Ti=Si+1;
for(int i=1;i<=m;i++){
girl[i]=read();
stain[T]+=girl[i];stain[n+i]-=girl[i];
add(n+i,T,maxn-girl[i]);//old graph
}
for(int i=1;i<=n;i++){
int C,D;
C=read();D=read();
id[i][0]=C;
add(S,i,D);//old graph
for(int j=1;j<=C;j++){
int u,v,w;
u=read();v=read();w=read();
++u;
//fin[i][j]=v;
stain[n+u]+=v;stain[i]-=v;
add(i,n+u,w-v);//new graph and old graph
id[i][j]=u;
low[i][j]=v;
}
}
for(int i=1;i<=n+m+2;i++)
if(stain[i]>0)add(Si,i,stain[i]),visual+=stain[i];
else if(stain[i]<0)add(i,Ti,-stain[i]);
add(T,S,maxn);
//cout<<"first dinic "<<endl;
int flow=Dinic(Si,Ti);
//cout<<flow<<endl;
if(flow!=visual)puts("-1\n");
else{
a[cnt].val=0;a[cnt^1].val=0;
flow+=Dinic(S,T);
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=a[j].next)if(j%2==0){
int v=a[j].to;
fin[i][v-n]+=a[j^1].val;
//cout<<"i "<<i<<" j "<<v-n<<" flow "<<fin[i][v-n]<<endl;
}
cout<<flow<<endl;
for(int i=1;i<=n;i++){
for(int j=1;j<=id[i][0];j++)
cout<<fin[i][id[i][j]]+low[i][j]<<endl;
}
cout<<endl;
}
}
return 0;
}