POJ 3020 Antenna Placement【題解報告|二分圖匹配】

題目大意

一個矩形中,有N個城市’*’,現在這n個城市都要覆蓋無線,若放置一個基站,那麼它至多可以覆蓋相鄰的兩個城市。問至少放置多少個基站才能使得所有的城市都覆蓋無線?

思路分析

在這道題上卡了很久,才接觸二分圖也沒什麼好的思路。這道題需要用到一個定理:

二分圖最小邊覆蓋 = 兩邊頂點數 - 最大匹配數
無向圖的最小邊覆蓋 = (二分圖兩邊頂點數 - 二分圖的最大匹配數)/2

下面列舉幾個二分圖問題的常用定理:

定理1:最大匹配數 = 最小點覆蓋數(這是 Konig 定理)
定理2:二分圖最小邊覆蓋 = 頂點數 - 最大匹配數
定理3:最大匹配數 = 最大獨立數

這些定理是需要掌握的基礎,但問題的關鍵是如何將要解決的問題歸約成我們熟悉的模型。也就是如何建圖的問題。
基站將兩個城市覆蓋可以看成是在兩個城市間連了一條邊,這樣問題變成了一個圖論問題,對於圖中的每一個聯通分量,至少用多少邊才能將所有點都覆蓋,這就是比較典型的無向圖最小邊覆蓋問題。

接下來講如何將無向圖最小邊覆蓋問題轉化爲二分圖的最小邊覆蓋問題的。

比如原來的無向圖是這樣的:

在這裏插入圖片描述

這裏經過一個叫做拆點的操作,其實就是將原圖複製了一份,顯而易見,拆點後圖的最小邊覆蓋數是原圖的兩倍。注意觀察裏面點的命名,一條邊總是連接一個實點和一個虛點(二分圖一條邊總是連接左邊的點後右邊的點)。

在這裏插入圖片描述
實際上經過拆點之後圖變成了一個二分圖(只是重排了一下點的位置,但它們之間的拓撲關係不變)

在這裏插入圖片描述

利用公式: 無向二分圖的最小路徑覆蓋 = 頂點數 – 最大二分匹配數/2

頂點數:就是用於構造無向二分圖的城市數,即進行“拆點”操作前的頂點數量

最大二分匹配書數所以要除以2,是因爲進行了“拆點”操作,使得匹配總數多了一倍,因此除以2得到原圖的真正的匹配數。

#define inf 0x3f3f3f3f
#define ll long long
#define vec vector<int>
#define P pair<int,int>
#define MAX 45

int T, h, w, ma[MAX][15], dx[4] = { 0,0,1,-1 }, dy[4] = { 1,-1,0,0 };
int belong[MAX * 15], vis[MAX * 15];
string s[45];
vec G[MAX*15];

void build(int x, int y) {
	for (int i = 0; i < 4; i++) {
		int xx = x + dx[i], yy = y + dy[i];
		if (xx < 0 || xx >= h || yy < 0 || yy >= w || s[xx][yy] == 'o')continue;
		G[ma[x][y]].push_back(ma[xx][yy]);
	}
}

bool match(int k) {
	for (int i = 0; i < G[k].size(); i++) {
		int v = G[k][i];
		if (!vis[v]) {
			vis[v] = 1;
			if (belong[v] == 0 || match(belong[v])) {
				belong[v] = k;
				return true;
			}
		}
	}
	return false;
}

int main() {
	cin >> T;
	while (T--) {
		cin >> h >> w;
		for (int i = 0; i < h; i++)cin >> s[i];

		//生成每個位置的編號
		int cnt = 1;
		for (int i = 0; i < h; i++)
			for (int j = 0; j < w; j++)
				if (s[i][j] == '*')
					ma[i][j] = cnt++;
				else ma[i][j] = 0;

		//建圖
		for (int i = 0; i < MAX * 15; i++)G[i].clear();
		for (int i = 0; i < h; i++)
			for (int j = 0; j < w; j++)
				if (s[i][j] == '*')
					build(i, j);

		//匹配
		int sum = 0;
		memset(belong, 0, sizeof(belong));
		for (int i = 1; i < cnt; i++) {
			memset(vis, 0, sizeof(vis));
			if (match(i))sum++;
		}
		cout << cnt - 1 - sum / 2 << endl;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章