1. 程式人生 > 其它 >WR:網流之神的傳奇一生

WR:網流之神的傳奇一生

\(\LARGE\text{“}\) 採菊東籬下,波撼岳陽城。\(\large {_”}\)

試見過,擬陣之交,因神的劍術沸騰爆裂。

試見過,資本之惡,因神的力量化雪紛飛。

是神的全知,讓無人知曉的文明之始得以傳世。

是神的全能,讓牢不可破的數理之樹兔死狐悲。

最大流

顯然。\(O(n^2m)\)

費用流

顯然。\(O(nmf)\)

上下界

loj 上有這三體的模板,懶得放連結了 = =

可行流

新建超源 \(SS\),超匯 \(TT\)

對於一條邊 \(p\to q:[l,r]\),那就相當於保證要 \(p\) 點必須要流出 \(l\) 的流量,\(q\) 點必須要得到 \(l\)

的流量,然後跑 \(p\to q: r-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|\)

。然後判斷是否 Dinic = \(\sum w_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)\)