hihocoder 1122 : 二分圖二•二分圖最大匹配之匈牙利算法

時間限制:10000ms
單點時限:1000ms
內存限制:256MB

描述

上一回我們已經將所有有問題的相親情況表剔除了,那麼接下來要做的就是安排相親了。因爲過年時間並不是很長,所以姑姑希望能夠儘可能在一天安排比較多的相親。由於一個人同一天只能和一個人相親,所以要從當前的相親情況表裏選擇儘可能多的組合,且每個人不會出現兩次。不知道有沒有什麼好辦法,對於當前給定的相親情況表,能夠算出最多能同時安排多少組相親呢?

同樣的,我們先將給定的情況錶轉換成圖G=(V,E)。在上一回中我們已經知道這個圖可以被染成黑白兩色。不妨將所有表示女性的節點記爲點集A,表示男性的節點記爲點集B。則有A∪B=V。由問題可知所有邊e的兩個端點分別屬於AB兩個集合。則可以表示成如下的圖:

同樣的,我們將所有的邊分爲兩個集合。集合S和集合M,同樣有S∪M=E。邊集S表示在這一輪相親會中將要進行的相親,邊集M表示在不在這一次進行。對於任意邊(u,v) ∈ S,我們稱u和v爲一組匹配,它們之間相互匹配。在圖G,我們將邊集S用實線表示,邊集M用虛線表示。得到下圖:

則原問題轉化爲,最多能選擇多少條邊到集合S,使得S集合中任何兩條邊不相鄰(即有共同的頂點)。顯然的,|S|<=Min{|A|, |B|}。

那麼能不能找到一個算法,使得能夠很容易計算出儘可能多的邊能夠放入集合S?我們不妨來看一個例子:

對於已經匹配的點我們先不考慮,我們從未匹配的點來做。這裏我們選擇A集合中尚未匹配的點(A3和A4)考慮:

對於A3點,我們可以發現A3與B4右邊相連,且都未匹配。則直接將(A3,B4)邊加入集合S即可。

對於A4點,我們發現和A4相連的B3,B4點都已經匹配了。但是再觀察可以發現,如果我們將A2和B2相連,則可以將B3點空出來。那麼就可以同時將(A2,B2),(A4,B3)相連。將原來的一個匹配變成了兩個匹配。

讓我們來仔細看看這一步:我們將這次變換中相關聯的邊標記出來,如下圖所示紫色的3條邊(A2,B2),(A2,B3),(A4,B3)。

這三條邊構成了一條路徑,可以發現這條路徑有個非常特殊的性質。虛線和實線相互交錯,並且起點和終點都是尚未匹配的點,且屬於兩個不同的集合。我們稱這樣的路徑爲交錯路徑。

再進一步分析,對於任意一條交錯路徑,虛線的數量一定比實線的數量多1。我們將虛線和實線交換一下,就變成了下面的圖:

在原來1個匹配的基礎上,我們得到了2個新的匹配,S集合邊的數量也增加了1。並且原來在已經匹配的點仍然是已經匹配的狀態。

再回頭看看A3點匹配時的情況:對於(A3,B4)這一條路徑,同樣滿足了交錯路徑的性質。

至此我們得到了一個找新匹配的有效算法:

選取一個未匹配的點,查找是否存在一條以它爲起點的交錯路徑。若存在,將該交錯路徑的邊虛實交換。否則在當前的情況下,該點找不到可以匹配的點。

又有對於已經匹配的點,該算法並不會改變一個點的匹配狀態。所以當我們對所有未匹配的點都計算過後,仍然沒有交錯路徑,則不可能找到更多的匹配。此時S集合中的邊數即爲最大邊數,我們稱爲最大匹配數。

那麼我們再一次梳理整個算法:

1. 依次枚舉每一個點i; 
2. 若點i尚未匹配,則以此點爲起點查詢一次交錯路徑。

最後即可得到最大匹配數。

在這個基礎上仍然有兩個可以優化的地方:

1.對於點的枚舉:當我們枚舉了所有A中的點後,無需再枚舉B中的點,就已經得到了最大匹配。
2.在查詢交錯路徑的過程中,有可能出現Ai與Bj直接相連,其中Bj爲已經匹配的點,且Bj之後找不到交錯路徑。之後又通過Ai查找到了一條交錯路徑{Ai,Bx,Ay,…,Az,Bj}延伸到Bj。由於之前已經計算過Bj沒有交錯路徑,若此時再計算一次就有了額外的冗餘。所以我們需要枚舉每個Ai時記錄B集合中的點是否已經查詢過,起點不同時需要清空記錄。

僞代碼

輸入

第1行:2個正整數,N,M(N表示點數 2≤N≤1,000,M表示邊數1≤M≤5,000)
第2..M+1行:每行兩個整數u,v,表示一條無向邊(u,v)

輸出

第1行:1個整數,表示最大匹配數


解法一:直接求所有的匹配,需要雙鏈接


還需要


代碼如下:

import java.util.Arrays;
import java.util.Scanner;

public class Main {
	boolean[] used;
	int[] re;
	public int solve(boolean[][] graph){
		int res=0;
		re=new int[graph.length];
		used=new boolean[graph.length];
		Arrays.fill(re,-1);
		for(int i=0;i<graph.length;i++){
			if(re[i]!=-1) continue;
			Arrays.fill(used,false);			
			if(find(graph,i)) res++;
		}
		return res;
	}
	public boolean find(boolean[][] graph, int k) {
		for(int i=0;i<graph.length;i++){
			if(graph[k][i]&&!used[i]){
				used[i]=true;
				if(re[i]==-1||find(graph,re[i])){           
					re[i]=k;
					re[k]=i;
					return true;
				}
			}
		}
		return false;
	}
	public static void main(String[] args){
		Scanner scan=new Scanner(System.in);
		Main main=new Main();
		int num=scan.nextInt();
		int count=scan.nextInt();
		boolean[][] graph=new boolean[num][num];
		for(int i=0;i<count;i++){
			int x=scan.nextInt()-1;
			int y=scan.nextInt()-1;
			graph[x][y]=true; graph[y][x]=true;
		}
		System.out.println(main.solve(graph));
	}
}


解法2:找增光路徑  結果爲(增光路徑+1)/2

代碼如下:

import java.util.Arrays;
import java.util.Scanner;

public class Main2 {
	boolean[] used;
	int[] re;
	public int solve(boolean[][] graph){
		int res=0;
		re=new int[graph.length];
		used=new boolean[graph.length];
		Arrays.fill(re,-1);
		for(int i=0;i<graph.length;i++){
//			if(re[i]!=-1) continue;
			Arrays.fill(used,false);			
			if(find(graph,i)) res++;
		}
		return (res+1)/2;
	}
	public boolean find(boolean[][] graph, int k) {
		for(int i=0;i<graph.length;i++){
			if(graph[k][i]&&!used[i]){
				used[i]=true;
				if(re[i]==-1||find(graph,re[i])){           
					re[i]=k;
					return true;
				}
			}
		}
		return false;
	}
	public static void main(String[] args){
		Scanner scan=new Scanner(System.in);
		Main2 main=new Main2();
		int num=scan.nextInt();
		int count=scan.nextInt();
		boolean[][] graph=new boolean[num][num];
		for(int i=0;i<count;i++){
			int x=scan.nextInt()-1;
			int y=scan.nextInt()-1;
			graph[x][y]=true; graph[y][x]=true;
		}
		System.out.println(main.solve(graph));
	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章