題目在這裡 http://poj.org/problem?id=3041
因為最近在準備找工作,所以一直在學習演算法,不過我覺得對於一個初學者來說,最難的不是弄懂一個演算法,二是如何將題目編程演算法的模型,然後編寫出程式。個人覺得這個過程應該就是靠多做題,多分析吧,培養自己的靈感來源。
這個題目大概的意思是說, 在一個N*N的矩陣中,有若干個隕石,飛船的大炮可以每次消滅一排或者一列,問如何使得開炮的次數最小,但是卻可以消滅掉所有的隕石。
題目分析:
可以將行和列做如下轉換:
上面的圖中,左圖表示行編號,右圖表示列編號,如果在(i,j)上有一顆隕石,我們將行i 和 列 j串連。
這樣每一條邊都表示一個隕石,如果我在第1行開炮,那麼和行1相連的所有邊就消除了。 所以這樣這個問題就轉換成了最小覆蓋問題。
引入點理論吧,在二分圖中:
匹配:
給定一個二分圖,在G的一個子圖G’中,如果G’的邊集中的任意兩條邊都不依附於同一個頂點,則稱G’的邊集為G的一個匹配
最大匹配:
在所有的匹配中,邊數最多的那個匹配就是二分圖的最大匹配了
頂點覆蓋:
在頂點集合中,選取一部分頂點,這些頂點能夠把所有的邊都覆蓋了。這些點就是頂點覆蓋集
最小頂點覆蓋:
在所有的頂點覆蓋集中,頂點數最小的那個叫最小頂點集合。
二分圖最大匹配的König定理
最大匹配 = 最小頂點覆蓋。此處不證明其正確性。
然後我們寫出程式如下:
#include <stdio.h>#include <memory.h>#define GRID_NUM 505int Grid[GRID_NUM][GRID_NUM];int matched[GRID_NUM];bool visited[GRID_NUM];int nGrid;bool dfs(int from){int i;for( i = 0;i < nGrid; i++){if(!visited[i] && Grid[from][i]){visited[i] = true;if(matched[i] == -1 || dfs(matched[i])){matched[i] = from;return true;}}}return false;}int main(){int nAstroid, j;int f, t;while(scanf("%d%d", &nGrid, &nAstroid) != EOF){memset(Grid, 0, sizeof(Grid));memset(matched, -1, sizeof(matched));for(j = 0; j < nAstroid; j++){scanf("%d%d", &f, &t);Grid[f-1][t-1] = 1;}int nIncrement = 0;for( j = 0; j < nGrid; j++){memset(visited, 0, sizeof(visited));if(dfs(j)){nIncrement++;}}printf("%d\n", nIncrement);}return 0;}