二分圖與網路流,ACMer需要掌握的一些模型

maple276發表於2024-06-28

寫在前面

前些日子的四川省賽,我們隊伍拿到了網路流C題的一血。看完題目和隊友討論了下,大概兩三分鐘就把題目正確做法討論出來了,但是寫程式碼過程還是比較笨拙的,建完圖後一度蒙圈甚至不知道自己建的是什麼意思,後來冷靜下來才把圖改對。

作為23年學校圖論專題的負責人,我覺得自己的網路流水平還是需要鍛鍊下。最近也是24暑假前集訓收官了,把今年的圖論專題網路流題目和以前做過的題目一起總結下。

我一直覺得,網路流題一是靠經驗,二是靠一些熟悉的模型,如果模型套對了,就可以享受快速過題甚至拿一血的樂趣。

這篇隨筆大概會涉及以下內容:二分圖匹配、最小點覆蓋、最大獨立集;最小割殘餘網路的處理、方案的輸出、切糕模型;最大權閉合子圖;方陣上的一些網路流模型;DAG轉二分圖的模型;

講述比較隨意,可能以後還會更新。

二分圖相關

二分圖匹配:

點數較少的時候可以使用 \(O(nm)\) 的匈牙利演算法,比較好寫。

其餘情況可以無腦網路流,dinic 演算法跑二分圖的複雜度約為 \(O(m\sqrt n)\),詳見 ICPC2023濟南E

最大權值匹配可以費用流,但最大費用最大流不一定是最大費用流(如下圖),為了讓其強制最大流,可以從每個左部結點向匯點連一條費用為 0,流量為 1 的邊。這個補流的思想還是比較有用的,下面會涉及到。

最小點覆蓋

最小點覆蓋的含義:選出圖中的一些點,滿足圖中所有邊的兩端至少有一個點被選中,最小的點集即為最小點覆蓋。

在二分圖中,最大匹配的數量和最小點覆蓋的點數是相同的。如果你將某題的題意轉化為了求二分圖的最小點覆蓋,那麼直接跑最大流就是答案。

在一般圖中,最大匹配的數量和最小點覆蓋的點數不一定相同,比如最簡單的一個三角形圖,最大匹配為1,最小點覆蓋卻是2。

一般圖最大匹配有多項式複雜度的帶花樹,而一般圖最小點覆蓋是npc。

我們知道最大匹配的方案很好輸出,匈牙利演算法自動匹配好了,最大流只需要看看殘餘網路的情況。但最小點覆蓋的方案就要稍微難找一些,Matrix67大佬的部落格裡有König定理的證明以及求出一組方案的過程,我們可以利用這個定理進行方案查詢:二分圖最大匹配的König定理及其證明

下面是匈牙利演算法求最小點覆蓋的程式碼:

#include <bits/stdc++.h>
#define ll long long

using namespace std;
const int N=1010;

inline int read() {
	int sum = 0, ff = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
	return sum * ff;
}

int n1,n2,m;
vector <int> e[N];
int vis[N],belong[N];  // index is the right_part of G(V,E)
bool tagx[N],tagy[N];

int DFS(int u) {
	for(int v : e[u]) {
		if(vis[v]) continue;
		vis[v] = 1;
		if(!belong[v] || DFS(belong[v])) { belong[v] = u; return 1; }
	}
	return 0;
}

void mark(int u) {
	if(tagx[u]) return ;
	tagx[u] = 1;
	for(int v : e[u]) {
		if(!tagy[v] && belong[v]) {
			tagy[v] = 1;
			mark(belong[v]);
		}
	}
}

int main() {
	int x,y;
	n1 = read(); n2 = read(); m = read();
	while(m--) {
		x = read(); y = read();
		e[x].push_back(y);
	}
	int ans = 0;
	for(int i=1;i<=n1;i++) {
		memset(vis, 0, sizeof(vis));
		ans += DFS(i);
	}
	cout<<"ans = "<<ans<<endl;
	memset(vis, 0, sizeof(vis));
	for(int i=1;i<=n2;i++) vis[ belong[i] ] = 1;
	for(int i=1;i<=n1;i++) if(!vis[i]) mark(i);
	for(int i=1;i<=n1;i++) if(!tagx[i]) cout<<i<<" ";
	cout<<endl;
	for(int i=1;i<=n2;i++) if(tagy[i]) cout<<i<<" ";
	cout<<endl;
	return 0;
}

/*

4 4 7
1 1
2 2
1 3
2 3
2 4
3 2
4 2

*/

其實除了König定理,直接在網路流的殘餘網路裡進行DFS也可以求出最小點覆蓋的方案,其過程與König定理的原理相似。

這個會在下一個大標題裡講。

最大獨立集

最大獨立集的含義是:選出圖中的一些點,滿足圖中不存在兩個端點都被選中的邊,最大的點集即為最大獨立集。

無論是在二分圖還是一般圖當中,最大獨立集和最小點覆蓋永遠是互補的關係,兩者相加即為總點數。證明非常簡單,思考個四五秒就懂了。

也就是說,二分圖的最大獨立集 = 總點數 - 最小點覆蓋 = 總點數 - 最大匹配。想要求二分圖最大獨立集點數,只需讓總點數(兩邊點數和)減去最大流。

若要求出一組方案,那和求最小點覆蓋的方法是完全相同的,因為它就是最小點覆蓋的補集。


如果你可以將題目按照以下步驟轉化:

1、轉化為二分圖,將點分成兩邊陣營。

2、兩邊陣營某些關係存在衝突(兩者只能選其一),給兩個點連一條邊。

3、要求最大可以保留的點數。

那就是最大獨立集的模型了,答案是總點數減去最小割。

最小割的殘餘網路

輸出最小割方案

首先總所周知,殘餘網路上流量為0的邊,並不代表它被割掉了。因為流一次,同一條路上可能會有很多邊的流量變為0,但我們只需要割一條邊。

如果題目需要我們給出一組最小割的方案(例如2021 ECfinal H),我們到底應該割掉哪些邊呢?


其實這是一個與最小割定義相關的問題。

最小割的定義是,將所有點劃分為 S 和 T 兩個集合,滿足源點在 S 中,匯點在 T 中,且所有從 S 到 T 的邊流量之和最小。那麼,從 S 到 T 的所有邊,就是我們要割掉的邊。

我們跑完最大流後,為什麼找不到新的增廣路了,就是因為 S 到 T 的所有邊都被割掉了。

如果我們從源點進行DFS,每次只沿著流量不為0的邊走,我們走到的所有點的集合其實就是點集 S 的一種方案。

下圖圖一,淺藍色邊為增廣路,紅色為最小割的方案,雖然源點到第一個點的邊流量也變成了0,但是它不能是最小割的邊。

下圖圖二,深藍色邊是跑完最大流後的殘餘網路,我們從源點開始DFS,綠色點即為 S 中的點,其餘點為 T 中的點。

左部點中未被綠色標記的點,是邊被割去的點,右部點中被綠色標記的點,是邊被割去的點。由此可以根據題意來輸出一組最小割方案。

事實上,你會發現從源點開始 DFS 的過程,與 König 定理中標記點的過程非常相似。沿著未滿流的原圖邊走,就是沿著二分圖中的非匹配邊走;沿著殘餘網路中的反向邊走,就是沿著二分圖中的匹配邊走。圖三中,紅色點是最小點覆蓋點集,藍色點為最大獨立集,可以發現最小點覆蓋和最小割是完全一致的。也就是說,想要求最小點覆蓋,實際上就是求一種最小割的方案。因此網路流圖建好以後,不需要上面程式碼中的 mark 函式,而是直接 DFS 求出 S 點集即可。

回過來看2021 ECfinal 的 H,是讓你求最多能有多少組WB交叉的2x2方塊,可以根據奇偶性翻轉顏色,將題意變為多少組純白或純黑的方塊。

經過這個轉化後,我們就可以套用上面提到的最大獨立集的三個步驟:

純白和純黑的方塊是兩個陣營,陣營內互不影響,可以劃分成一個二分圖;如果兩個方塊有相交部分,那就不能同時滿足一黑一白,因此將其左右連邊;最後求出最大獨立集就是最多可以留多少純色方塊。

換個方式,用最小割來理解,就是把所有衝突的路徑,透過割掉左邊點或者割掉右邊點,使其無法形成增廣路。

可以看出,最大獨立集和最小割的思想是同源的。


另外從OIwiki搬來一點關於最小割方案的補充:

如果需要在最小割的前提下最小化割邊數量,那麼先求出最小割,把沒有滿流的邊容量改成無窮大,滿流的邊容量改成1,重新跑一遍最小割就可求出最小割邊數量;如果沒有最小割的前提,直接把所有邊的容量設成1,求一遍最小割就好了。

下面左圖為原圖上最小割的方案,右圖為所有滿流邊容量改為1後的最小割方案:

切糕模型

BZOJ 3144 [HNOI2013] 切糕

一個比較經典的省選題,這題最小割的建圖方式還是比較妙的,不過我們也可以從中提煉出一個模型來。

為什麼要把切糕這題放在這裡講,因為提煉出的模型和上面的最小割方案有很大的關聯。


先回顧下這題的建圖方式:如果相鄰兩條路所切的點距離超過d,那麼我們認為不滿足題意,因此從一條路的下游點,向相鄰路距離為d的上游點連一條邊權為 inf 的邊,這樣如果這兩條路選點距離超過d,還會存在一條額外的增廣路來進行修正。(沒圖不好理解,具體看原題題解)

這個做法比較難想到,會讓人覺得難以理解,我們換個角度進行題意轉化:

相鄰兩條路選的兩個點距離不超過d,如果我們把所有上游點向距離為d的下游點連一條新邊(如下圖所示),那麼原題的要求就變成了:任意一條從源點到匯點的路徑上,只能割一條邊,求最小割。(由於源點和匯點相鄰的邊不會被割掉,所以每條路最上游和最下游的點不需要連新邊)

為了方便描述,我就把這個任意一條路徑只割一條邊的模型叫做切糕模型了。

而這個模型的解決思路也很簡單:所有的邊,複製一條流量為 inf 的反向邊,再跑最小割就行了,求方案也是從源點DFS即可。(注意這個前提是所有的點都在源點到匯點的路徑上,如果不是,去除那些無用點,再複製反向邊)


關於證明,需要好幾張圖,我懶得畫了。同校同學可以看下23年暑假前集訓圖論專題 Z 題的ppt題解。

簡單來講,可以考慮什麼時候會出現跑完最大流後一條路上被割兩條邊,那一定不可能是隻有一條路。在被割掉的兩條邊中間,一定會有一條從源點過來的路,以及一條通往匯點的路,如果中間建立了流量無窮大的反向邊,就可以使源點過來的路和通往匯點的路再產生新的增廣路,換一條割掉的邊,使原來路徑上的兩條邊不會同時被割掉。

其他的細節可以自行思考下。

平面圖最小割

參考經典的bzoj第一題狼抓兔子。如果題目給的是一張平面圖,要求出最小割,那麼會有一個比較有趣的性質,就是 S 和 T 兩個集合可以被一條線劃分到平面的兩邊。

S到T的每一條邊,合起來在對偶圖中可以形成一條完整的路徑,我們只需求出對偶圖中的最短路徑,就可以求出原圖中最小割的方案。


另外還有一道和網路流不相干的題目:CF325D,題目大意是實時判斷一個圓柱面是否被割開成上下兩部分。

雖然不是網路流題目,但是也用到了 平面圖最小割 轉 對偶圖最短路 的思想。

雖然這題是圓柱,但是可以用化環為鏈的思想,把圓柱展開成平面並複製一份,將上下兩部分割裂開,其實就是求一條完整的路徑,從左邊某一個點出發,到達右邊複製平面的同一個點。

ICPC2023濟南E

ICPC2023濟南E

這道題目我們需要統計新增一條邊後產生增廣路的情況數。

也是在殘餘網路上進行方案統計,不過和最小割方案不同,新增加的邊不能只滿足 “起點在 S 集合,終點在 T 集合”,還要滿足這條邊的終點能夠從殘餘網路中到達匯點。

所以這題需要兩遍DFS,一遍從源點出發走正向邊,一遍從匯點出發走反向邊,求出兩個點集。新增加的邊起點和終點在這兩個點集內才算合法。

最大權閉合子圖

最大權閉合子圖是這樣一類問題:

給出一個類似於2-sat的邏輯圖,一條邊 u 到 v 的含義為選了 u 這個結點,就一定要選 v 這個點。每個點有一個正權值或負權值,如何選點使得總權值和最大。(在這種邏輯圖當中,選出的子圖一定是閉合子圖)

一般圖的最大權閉合子圖

這類問題並不一定是二分圖,一般圖的最大權閉合子圖是同樣的做法。

首先把點分為兩個點集,正權的點放在左側,由源點連一條流量為權值的邊;負權的點放在右側,連向匯點一條流量為負權絕對值的邊;其他邊正常連線,流量為無窮大。

答案為所有正權的和 - 最大流。證明略


如何輸出方案呢?

其實和求最小割方案很相似,甚至更簡單。

直接說結論:從源點出發沿著流量不為0的邊DFS,走到的點集 S 就是答案的子圖。

如果說和求最大獨立集的區別,那就是最大權閉合子圖不一定是二分圖。

2024四川省賽C

gym105222C

這題要求我們使立方體滿足,左下角都是黑色,右上角都是白色。

一開始我想的是切一個面,還以為是切糕模型之類的,但是貢獻不是來自切斷鄰邊,而是翻轉顏色。

隊友馬上想到了關鍵的轉化:如果一個點是白色,那麼這個點上,右,前個方向的點都應該是白色。

我們先將所有點變成黑色點,需要一個初始的代價cost。然後如果一個點被選擇,代表將這個點染白,這個點指向的所有點,也需要染白。如果一個點最初是黑色,那麼將它染白需要花錢,如果一個點最初是白色,那麼由黑變白其實是反悔操作,可以賺錢。賺錢的點就是最大權閉合子圖中的正權點,花錢的就是負權點。一共能賺到正權和-最大流,所以最後的總花費就是cost-(正權和-最大流)。

我尋思了一下,發現cost就是正權和啊,所以最後答案直接就是最大流了。

這題還有個要求,左下角必須黑,右上角必須白,那就把這兩個點單獨算花費,不要連入網路流建圖中。

方陣上的一些網路流模型

有很多網路流的題目是建立在方陣上的。

比如網路流24題裡就有棋盤類的題目,國際象棋中,相永遠走不到另一種顏色的位置上,而馬每次必定會走到另一種顏色的位置上。這種天然的奇偶顏色關係,就可以建立起二分圖模型。

方陣中經常會出現只有上下左右連線,沒有斜邊連線的情況,因此不存在奇數環,很容易讓人聯想到二分圖的模型。

有時並不是方陣中原本的點進行建圖,而是原方陣中的邊,或者整體的某個塊兒,視作一個點,也很容易變成網路流的模型。

這裡舉例說明方陣轉網路流的一些模型。

2021 ECfinal H

2021 ECfinal H,還是上面那道題。

首先這個WB相間就非常迷惑人,如果想不到按照奇偶性進行顏色翻轉,就很容易想偏。

在方陣平面上,相鄰位置的互斥關係可以轉化為網路流二分圖的中間邊,比如這題中,相鄰的2x2方塊不可能是一黑一白,存在著互斥關係。

不過這題不是單純的將所有塊兒分到二分圖的兩側,因為有些方塊是四個問號,既可以是白色塊,也可以是黑色塊。因此每個位置需要拆成兩個點,左側點表示將這個位置塗成全白,右側點表示將這個位置塗成全黑,因此同一個位置的左右兩個點也是互斥的,需要連一條邊。再將其他八個方向有重疊部分的互斥關係連邊。跑一個最大獨立集。

CF1404E

CF1404E

用長條鋪滿方陣中要求的位置,題意就非常的網路流。

因為長條是不允許拐彎的,因此我們不允許有L型的幾個位置合在一起的情況。我們把兩個位置合一起的操作,看做是選擇兩個位置中間的點,那麼不允許L型的合併,也就是不允許一個位置同時選擇相鄰的兩個點。

將這些有衝突的點進行連邊,我們會發現由於平面的性質,它也是一張二分圖,跑一個最大獨立集,就是最多能進行幾次合併。

每一次合併可以減低一點費用,將 '#' 的數量減去合併的最大數量為最終答案。


另外再講一個和這題題意很像的,但正解不是網路流:江蘇省賽J

題意是用一些1*k的骨牌覆蓋方陣,骨牌不能重疊或者有相鄰邊,求最大能覆蓋的權值。

骨牌沒有相鄰邊意味著選擇的所有點不能出現L型的三個點,而不能出現L型的三個點,就是任意2x2的小方塊內最多選兩個點。

本來以為可以建成二分圖,左邊點表示2x2的小方塊,右邊點表示每一個位置,左邊點連匯點流量是2來控制方塊內點數。結果發現不行,這條流量為2的邊,並不能限制方塊內的點數,因為這些位置也可以從其他相鄰的方塊找流量流進來。

目前還沒想到網路流做法,估計是沒法做。資料範圍很小,正解是狀壓,不在這裡講了。

DAG轉二分圖

售貨員問題?

先來講一個很像售貨員問題的題目:

n 個點 m 條邊的 DAG,你可以花費 \(a_i\) 傳送到 \(i\) ,求每個點恰好經過一次走完所有點的最小花費。


有個上下界的模型可以解決這個問題,不過沒有必要,可以用按照出度入度拆點的方式將DAG轉成二分圖來解決。

具體的,每個點拆成左右側兩個點;由於每個點恰好經過一次,視作入度為1,每個右側點向匯點連一條流量為1的邊;每個點可以走到其他點一次,視為出度為1,從源點向左側點連一條流量為1的邊;原圖中的邊,從左側點連線到右側點,流量為1,費用為原圖邊權。

這時跑費用流。中間的某條邊滿流,說明路徑中選擇了這個點;連向匯點的邊滿流,說明這個點被到達。

現在的圖最大流不是n,這是因為在沒有傳送時,一些點是可能無法到達的。比如這題沒說一開始在DAG中的起點,而是透過傳送來到DAG中,我們考慮將傳送的路線加入二分圖中。

透過原圖中的邊從 u 走到點 v ,也就是二分圖左側點 u 連向右側點 v 的這條路;直接傳送到點 v ,就是不需要花費左側點的出度(每個點的出度只能用一次),而是直接從源點連一條到右側點 v ,費用為 \(a_v\) 的邊。

將這兩種移動方式的邊都連上,圖就算建完了,跑費用流就是每個點經過一次的答案。

可以發現,這題中從源點連向右側點這一個補流的操作,和上面講到的二分圖最大權匹配時的補流思想是很相似的。

最小生成樹?

再講一個很像最小生成樹的問題:CF277E

平面上有一些點,每個點只能向高度小於它的點連邊,且最多連出兩條邊,邊權為兩點歐幾里得距離,求連線所有點的最小邊權。也就是,求出一棵最小生成二叉樹。


與上一個例題思路一致,我們將每個點拆成二分圖左右兩點,左邊點代表出度,右邊點代表入度。

每個點最多連出兩條邊,因此從源點向左邊點連一條流量為2的邊;每個點只有一個入度,因此右側點向匯點連一條流量為1的邊;而中間所有可以連線的邊,都連一條流量為1,費用為歐幾里得距離的邊。

跑費用流,答案就是最小生成二叉樹。

這題沒有指定起點,但是預設從最高的點出發,最高的點入度為0,它不需要向匯點連邊。如果最高的點不止一個,那麼輸出無解。如果右側點連向匯點的邊不能滿流,說明也是無解,含義就是它們無法獲得足夠多的入邊。

為什麼兩題都需要保證是DAG?

我們都知道售貨員問題是npc的,因為這種建圖的前提是DAG。

我們再來思考下兩道題的路線方案:

第一題中,我們可以使用傳送,傳送完之後可以從落點走一條鏈,所有的鏈不重不漏覆蓋所有的點。

第二題中,二叉樹的第二條出邊也相當於傳送到了該點,並從該點開始走一條鏈,只不過這條鏈的起點掛在了以前經過的點上。

這樣一來,我們的鏈在DAG中都是有始有終的,一個出邊對應另一個點的入邊。

如果DAG改成了一般圖,就打破了這個平衡,我們甚至可以不使用傳送,而是在二分圖中跑出一個 1->2, 2->3, 3->1 的環作為費用,這樣同樣是滿足了每個點訪問一次,但是卻無法找到一條進入環的路線,無論是從其他點還是傳送。

第二題也不能是DAG,因為我們的建圖只能保證:每個點出邊上限為2,入邊必須為1,但無法保證圖的聯通性問題。因為這兩個限制在一般圖中可能會以基環樹森林的形式出現,即某個點出邊連向了自己的祖先,從而認為祖先結點不需要從更高的祖先連邊,造成圖不連通。而在DAG中,只要規定了入邊為1,那麼一定是生成的子圖一定是一棵樹。

寫在後面

感覺,也沒什麼需要寫在後面的了。

就是不定期會更新吧,如果以後看到類似的模型也會丟到這篇隨筆裡來。

上下界可能也會做些總結,不過就不往這裡丟了,這篇已經夠長了。

真能看完的讀者希望能有點收穫吧,哪怕只是幾道例題的指路也好。

相關文章