網路流-最小割

C//發表於2024-07-15

網路流-最小割

一些定義:

割:

對於一個網路流圖G=(V,E),其割的定義為一種點的劃分方式:將所有的點劃分為ST=V-S兩個集合,源點s屬於S,匯點t屬於T

割的容量:

指連線S中的點和T中的點的邊容量的總和,符號為C(S,T)

最小割問題:

求一個割(S,T)使得割C(S,T)最小

最大流最小割定理:最小割等於最大流

證明:詳情參見:OI Wiki 最大流最小割定理

最小割模型:

問題模型 1

有 n 個物品和兩個集合 A,B,如果一個物品沒有放入 A 集合會花費 a_i,沒有放入 B 集合會花費 b_i;還有若干個形如 u_i,v_i,w_i 限制條件,表示如果 u_i 和 v_i 同時不在一個集合會花費 w_i。每個物品必須且只能屬於一個集合,求最小的代價。

這是一個經典的 二者選其一 的最小割題目。我們對於每個集合設定源點 s 和匯點 t,第 i 個點由 s 連一條容量為 a_i 的邊、向 t 連一條容量為 b_i 的邊。對於限制條件 u,v,w,我們在 u,v 之間連容量為 w 的雙向邊。

注意到當源點和匯點不相連時,代表這些點都選擇了其中一個集合。如果將連向 s 或 t 的邊割開,表示不放在 A 或 B 集合,如果把物品之間的邊割開,表示這兩個物品不放在同一個集合。

問題模型 2

最大權值閉合圖,即給定一張有向圖,每個點都有一個權值(可以為正或負或 0),你需要選擇一個權值和最大的子圖,使得子圖中每個點的後繼都在子圖中。

做法:建立超級源點 s 和超級匯點 t,若節點 u 權值為正,則 s 向 u 連一條有向邊,邊權即為該點點權;若節點 u 權值為負,則由 u 向 t 連一條有向邊,邊權即為該點點權的相反數。原圖上所有邊權改為 \infty。跑網路最大流,將所有正權值之和減去最大流,即為答案。

幾個小結論來證明:

1.每一個符合條件的子圖都對應流量網路中的一個割。因為每一個割將網路分為兩部分,與 s 相連的那部分滿足沒有邊指向另一部分,於是滿足上述條件。這個命題是充要的。
2.最小割所去除的邊必須與 s 和 t 其中一者相連。因為否則邊權是 \infty,不可能成為最小割。
3.我們所選擇的那部分子圖,權值和 = 所有正權值之和 - 我們未選擇的正權值點的權值之和 + 我們選擇的負權值點的權值之和。當我們不選擇一個正權值點時,其與 s 的連邊會被斷開;當我們選擇一個負權值點時,其與 t 的連邊會被斷開。斷開的邊的邊權之和即為割的容量。於是上述式子轉化為:權值和 = 所有正權值之和 - 割的容量。
4.於是得出結論,最大權值和 = 所有正權值之和 - 最小割 = 所有正權值之和 - 最大流。

最小割就是最小花費。


例題:4020. 【雅禮聯考DAY02】Revolution

題目大意:

地圖是個矩形的網格。
可以花費一定金錢在一些格子投資。
被投資的格子或者四連通的格子都被投資的話,我就可以獲得該格子的收益。
利益最大化是作為商人的基本準則,但這是計算機的任務,拜託您了。

輸入:

第一行兩個數 n,m(n,m ≤ 20),表示矩形的長和寬。
接下來 n 行,每行是 m 個字元組成的字串,描述投資的花費。
接下來 n 行,每行是 m 個字元組成的字串,表示該格子的收益。
花費和收益按照一種奇葩的方式給出:
字元 數
‘0’ -’ 9’ 0-9
‘a’ -’ z’ 10-35
‘A’ -’ Z’ 36-61

輸出:

一個數,表示收益的和減去投資的和的最大值。

思路:

最小割?最大流? idk:P
但是相同點:黑白染色(遇見方格建圖的時候多可以進行此操作)
嘗試最大流:
設最大流為答案,則對每個白點進行…… 肯定是不行的!!!( 經過了兩個半小時的嘗逝:( )
那麼嘗試最小割:
設答案=價值總和-最小割,那麼每個點可以拆為三個點q1,q2,q3
對於白點從q1向q2連一條容量為cost(成本)的邊,從q2向q3連一條容量為w(價值)的邊
那麼在最小割中,容量為cost的邊的狀態與容量為w的邊的狀態,分別對應了單獨投資這個格子不投資這個格子的狀態
這樣建圖能滿足的原因是假如q1,q2,q3同時屬於一個集合時,此時的割一定不是最小割,但是還有一個點四聯通的時候,它的價值也是可以被計算的,那麼設一個白點為i點,其上、下、左、右四個黑點分別對應a,b,c,d。那麼i3可以與a1,b1,c1,d1連一條容量為inf(無窮大)的邊,a1,b1,c1,d1分別與a2,b2,c2,d2連一條容量為w的邊,a2,b2,c2,d2分別於a3,b3,c3,d3連一條容量為cost的邊,那麼此時的圖初步滿足了我們要求的所以性質。
但是這樣操作會使得當i處於用四聯通的方式去取得其價值時,黑點的w邊與cost邊都會被割在同一個集合中。那我們不將i3與a1,b1,c1,d1連一條容量為inf(無窮大)的邊,而是將i3與a2,b2,c2,d2連一條容量為inf的邊,那麼此時再將i2與a3,b3,c3,d3連一條容量為inf的邊,將s與i1連一條inf的邊,將a3,b3,c3,d3與t連一條容量為inf的邊,就可以讓只選黑點格子的狀態也被包含。

程式碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const long long inf=1e18;
int read(){
	char c=getchar();
	while ((c>='0'&&c<='9')==false&&(c>='a'&&c<='z')==false&&(c>='A'&&c<='Z')==false) c=getchar();
	if (c>='0'&&c<='9') return c-'0';
	else if (c>='a'&&c<='z') return c-'a'+10;
	else return c-'A'+36;
}
long long max(long long a,long long b){
	return a>b?a:b; 
}
long long min(long long a,long long b){
	return a<b?a:b;
}
int en=1,fi[1310],cur[1310];
struct rec{
	int e,nex;
	long long d;
}z[1000010];
void add(int s,int e,long long d){
	z[++en].e=e;
	z[en].d=d;
	z[en].nex=fi[s];
	fi[s]=en;
}
struct que{
	int l,r;
	int a[100010];
	void memsets(){
		l=1,r=0;
	}
	void pop(){
		l++;
	}
	int front(){
		return a[l];
	}
	void push(int x){
		a[++r]=x;
	}
	bool empty(){
		return r-l+1==0;
	}
}q;
int d[10010],cnt[10010];
int n;
void bfs(int s,int t){
	for (int i=1;i<=n;i++){
		d[i]=-1;
	}
	d[t]=0;
	cnt[0]=1;
	q.push(t);
	int x;
	while (q.empty()==false){
		x=q.front();
		q.pop();
		for (int i=fi[x];i!=0;i=z[i].nex){
			if (d[z[i].e]==-1){
				d[z[i].e]=d[x]+1;
				++cnt[d[z[i].e]];
				q.push(z[i].e);
			}
		}
	}
	return ;
}
long long max_flow;
long long dfs(int x,long long flow,int s,int t){
	if (x==t) return flow;
	long long used=0,w;
	for (int i=cur[x];i!=0;i=z[i].nex){
		cur[x]=i;
		if (z[i].d==0||d[z[i].e]+1!=d[x]) continue;
		w=dfs(z[i].e,min(flow-used,z[i].d),s,t);
		z[i].d-=w;z[i^1].d+=w;
		used+=w;
		if (used==flow) return used;
	}
	--cnt[d[x]];
	if (cnt[d[x]]==0) d[s]=n+1;
	++d[x];
	++cnt[d[x]];
	return used;
}
void ISAP(int s,int t){
	bfs(s,t);
	while (d[s]<n){
		for (int i=1;i<=n;i++) cur[i]=fi[i];
		max_flow+=dfs(s,inf,s,t);
	}
	return ;
}
int len_k,len_c;
int q1(int x,int y){
	return (x-1)*len_k+y; 
}
int q2(int x,int y){
	return (x-1)*len_k+y+len_c*len_k;
}
int q3(int x,int y){
	return (x-1)*len_k+y+len_c*len_k*2;
}
bool vis(int i,int j){
	return (i+j)%2==1;
}
void add_edge(int s,int e,long long d){
	add(s,e,d);add(e,s,0); 
}
int l[]={1,-1,0,0},r[]={0,0,1,-1};
int a_c[30][30],a_w[30][30];
int s,t;
long long ans;
void init(){
	scanf("%d%d",&len_c,&len_k);
	s=len_k*len_c*3+1;
	t=s+1;
	n=t;
	for (int i=1;i<=len_c;i++){
		for (int j=1;j<=len_k;j++){
			a_c[i][j]=read();
		}
	}
	for (int i=1;i<=len_c;i++){
		for (int j=1;j<=len_k;j++){
			a_w[i][j]=read();
			ans+=a_w[i][j];
		}
	}
	for (int i=1;i<=len_c;i++){
		for (int j=1;j<=len_k;j++){
			int z1=q1(i,j),z2=q2(i,j),z3=q3(i,j),x,y;
			if (vis(i,j)==true){//白點 
				
				add_edge(s,z1,inf);
				add_edge(z1,z2,a_c[i][j]);
				add_edge(z2,z3,a_w[i][j]);
				
				for (int k=0;k<=3;k++){
					x=i+l[k];y=j+r[k];
					if (x<1||y<1||x>len_c||y>len_k) continue;
					add_edge(z2,q1(x,y),inf);
					add_edge(z3,q2(x,y),inf);
				}
				
			}
			else{//黑點 
			
				add_edge(z1,z2,a_w[i][j]);
				add_edge(z2,z3,a_c[i][j]);
				add_edge(z3,t,inf);
			
			}
		}
	}
}
void print(){
	printf("%lld",ans-max_flow);
}
int main(){
	init();
	ISAP(s,t);
	print();
	return 0;
}

相關文章