WR:網流之神的傳奇一生
\(\LARGE\text{“}\) 採菊東籬下,波撼岳陽城。\(\large {_”}\)
試見過,擬陣之交,因神的劍術沸騰爆裂。
試見過,資本之惡,因神的力量化雪紛飛。
是神的全知,讓無人知曉的文明之始得以傳世。
是神的全能,讓牢不可破的數理之樹兔死狐悲。
最大流
顯然。\(O(n^2m)\)。
費用流
顯然。\(O(nmf)\)。
上下界
loj 上有這三體的模板,懶得放連結了 = =
可行流
新建超源 \(SS\),超匯 \(TT\)。
對於一條邊 \(p\to q:[l,r]\),那就相當於保證要 \(p\) 點必須要流出 \(l\) 的流量,\(q\) 點必須要得到 \(l\)
於是我們就讓 \(p\) 點連向 \(TT\),邊權為 \(l\);\(SS\) 點連向 \(q\) 邊權為 \(l\)。然後跑 Dinic。如果最大流不是所有的 \(l\) 之和,就說明有 \(p\) 沒有貢獻出 \(l\) 的流量,或者有 \(q\) 沒有收到 \(l\) 的流量。(不然這些 \(l\) 的流量,會存在於 \(SS,TT\) 的連邊中)。
實際操作中,我們給每個點設一個數組 hav
代表要匯入/匯出的流量總和。如果大於 0 就是要匯入的流量,連邊 \(SS\to i:|hav_i|\);小於 0 就是要匯出的流量,連邊 \(i\to TT:|hav_i|\)
最大流
大體思路:先跑出一組可行流,再在殘量網路上繼續跑,看看能不能再流一些流量。
首先連邊 \(T\to S:\inf\),這樣就可以看成沒有源點/匯點的情況了(因為源點是無限輸出,匯點是無限輸入)然後按照上文以 \(SS,TT\) 為源匯跑可行流。
這樣我們得到了一組可行解。將 \(T\to S:\inf\) 斷邊。再以 \(S,T\) 為源匯繼續跑一遍最大流即可。
最小流
參考最大流的做法,但是最後不是“再流一些流量”,而是要“再退回流量”。
於是把最後一步改為:以 \(S\) 為匯,以 \(T\) 為源(也就是 swap(S,T)
最長反鏈
https://www.luogu.com.cn/problem/P4298 然而不會第二問。
首先根據胡克定律(Dilworth定理):最長反鏈 = 最小可重鏈覆蓋。
然後我們對原圖跑一遍傳遞閉包,就會最長反鏈 = 最小不可重鏈覆蓋。(感性理解一下)
那麼就可以這樣理解:一開始每個點都是獨立的鏈,每次可以把兩條鏈首尾相接,最多可以操作幾次。
發現,一個點在最終的鏈上,最多有一個前驅和一個後繼,那麼就把一個點拆成兩個點 \(x_{in},x_{out}\)。對於一條邊 \(x\to y\) 連邊 \(x_{out}\to y_{in}\)(十分好理解!)。於是就有一個二分圖,左邊是全部的 out,右邊是全部的 in。跑二分圖最大匹配即可。假設最大匹配為 \(val\),答案就是 \(n-val\)。
二分圖最大匹配 - 匈牙利演算法
DFS
#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define mar(o) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
#define lon long long
using namespace std;
const int n7=101234,m7=1012345;
struct dino{int to,nxt;}e[m7];
int n1,n2,m,ecnt,fst[n7],ans,tim,vis[n7],mat[n7];
int rd(){
int shu=0;bool fu=0;char ch=getchar();
while( !isdigit(ch) ){if(ch=='-')fu=1;ch=getchar();}
while( isdigit(ch) )shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
return fu?-shu:shu;
}
void edge(int p,int q){
ecnt++;
e[ecnt]=(dino){q,fst[p]};
fst[p]=ecnt;
}
bool dfs(int o){
vis[o]=tim;
mar(o){
if(mat[v])continue;
mat[o]=v,mat[v]=o;
return 1;
}
mar(o){
int z=mat[v];
if(vis[z]==tim)continue;
mat[o]=v,mat[v]=o,mat[z]=0;
if( dfs(z) )return 1;
mat[o]=0,mat[v]=z,mat[z]=v;
}
return 0;
}
int main(){
n1=rd(),n2=rd(),m=rd();
rep(i,1,m){
int p=rd(),q=n1+rd();
edge(p,q),edge(q,p);
}
rep(i,1,n1){
if(!mat[i])tim++,dfs(i);
}
rep(i,1,n1){
if(mat[i])ans++;
}
printf("%d\n",ans);
return 0;
}
BFS
#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define mar(o) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
#define lon long long
using namespace std;
const int n7=101234,m7=1012345;
struct dino{int to,nxt;}e[m7];
int n1,n2,m,ecnt,fst[n7],ans,tim,vis[n7],mat[n7],pre[n7];
int head,tail,que[n7];
int rd(){
int shu=0;bool fu=0;char ch=getchar();
while( !isdigit(ch) ){if(ch=='-')fu=1;ch=getchar();}
while( isdigit(ch) )shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
return fu?-shu:shu;
}
void edge(int p,int q){
ecnt++;
e[ecnt]=(dino){q,fst[p]};
fst[p]=ecnt;
}
void aug(int o){
while(o){
int tmp=mat[ pre[o] ];
mat[o]=pre[o];
mat[ pre[o] ]=o;
o=tmp;
}
}
void bfs(int o0){
tim++;
head=tail=1,que[head]=o0,vis[o0]=tim;
while(head<=tail){
int o=que[head];head++;
mar(o){
if(vis[v]==tim)continue;
pre[v]=o;
if(!mat[v]){aug(v);return;}//return 1;
else{
vis[v]=vis[ mat[v] ]=tim;
tail++,que[tail]=mat[v];
}
}
}
//return 0
}
int main(){
n1=rd(),n2=rd(),m=rd();
rep(i,1,m){
int p=rd(),q=n1+rd();
edge(p,q),edge(q,p);
}
rep(i,1,n1){
if(!mat[i])bfs(i);
}
rep(i,1,n1){
if(mat[i])ans++;
}
printf("%d\n",ans);
return 0;
}
如果邊帶權怎麼辦?費用流!(似乎也有匈牙利做法,然而懶。)
(但是如果有負權邊可能會影響,要刪除此邊)
一般圖最大匹配 - 帶花樹
https://www.luogu.com.cn/blog/happydef-blog/yi-ban-tu-zui-da-pi-pei-xue-xi-bi-ji
\(O(n^3)\)。