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