演算法思路:回溯,暴力。
以下轉載了兩個版本:
版本1
0.032msAC。
// Servicing Stations (服務站)// PC/UVa IDs: 110804/10160, Popularity: B, Success rate: low Level: 3// Verdict: Accepted // Submission Date: 2011-08-10// UVa Run Time: 0.056s// 著作權(C)2011,邱秋。metaphysis # yeah dot net//// 該題目的實質是求圖的最小支配集頂點個數。求圖的最小支配集問題是 NP 完全問題,目前無高效的演算法。// 既然要枚舉集合的所有子集,那就枚舉吧,但是等一等,枚舉需要個順序,先枚舉元素數量小的子集,要不// 然,先枚舉元素個數大的子集可能會浪費時間,畢竟從元素個數小的子集枚舉到元素個數大的子集可以保證// 找到最小支配集而不再浪費更多的時間,哎呀,時間是金錢,不是嗎?可能什麼時候某個強人發現 NP 難問// 題的 P 時間演算法,或者證明 P = NP 那就好了,希望在有生之年能夠看到!!//// 為了提高效率,壓縮程式已耗用時間,採用了以下最佳化方法:// (1)若圖可以拆分為多個不相連的子圖,則先予拆分,然後對子圖求最小支配集的頂點個數相加(之前由// 於未拆分圖,導致了多次 TLE)。// (2)對於求兩個集合的並採用了位操作,事先將某個頂點的鄰接表表示為一個整數以便用與操作來代替集合// 的並。// (3)枚舉時,先考慮度數大的頂點。//// 枚舉方法參考了 [J. Loughry, J.I. van Hemert, L. Schoofs, Efficiently Enumerating// the Subsets of a Set, 2000]#include <iostream>#include <vector>#include <algorithm>#include <set>using namespace std;#define MAX_TOWN 35typedef long long unsigned LLUINT;bool finish;int minimum;vector < LLUINT > edge;LLUINT target_tag, origin_tag;// 檢查頂點集合是否為支配集。void check(int flag[], int position){int index = 0;LLUINT new_tag = origin_tag, old_tag = origin_tag;for (int i = 0; i < edge.size(); i++){if ((index < position) && (flag[index] == i)){new_tag |= edge[i];if (new_tag > old_tag)old_tag = new_tag;elsereturn;index++;}}if (new_tag == target_tag){minimum = index;finish = true;}}// 按 Banker’s Sequence 枚舉圖的子集。void generate(int flag[], int position, int positions){if (finish)return;if (position < positions){if (position == 0){for (int i = 0; i < edge.size(); i++){flag[position] = i;generate(flag, position + 1, positions);}}else{for (int i = flag[position - 1] + 1; i < edge.size(); i++){flag[position] = i;generate(flag, position + 1, positions);}}}elsecheck(flag, position);}// 枚舉圖向量的子集以判斷是否是一個支配集。void enumerating_subset(){for (int i = 1; i <= edge.size(); i++){int * flag = new int[edge.size()];generate(flag, 0, i);delete [] flag;if(finish)return;}}bool cmp(LLUINT x, LLUINT y){return x > y;}// 擷取圖的最小支配集頂點數(MDSN)。int mdsn(vector < vector < int > > &vertex){int base = 0;origin_tag = 0;target_tag = 0;vector < bool > dirty(vertex.size());fill(dirty.begin(), dirty.end(), false);// 清掉度為 0 的點。度為 0 的點和其他點都無通路,則該圖的最小支配集必須要包括// 該頂點。則表示最小支配集頂點個數的變數 base 需增加 1。for (int i = 0; i < vertex.size(); i++){if (vertex[i].size() == 0){base++;origin_tag |= ((LLUINT)1 << i);}if (vertex[i].size() == 1 && dirty[i] == false){dirty[i] = true;if (dirty[vertex[i][0] - 1] == false){base++;dirty[vertex[i][0] - 1] = true;}}target_tag |= ((LLUINT)1 << i);}// 清掉度為 1 的點。度為 1 的點表明該頂點 A 只與其他一個頂點 B 相串連,則可// 將 B 計入最小支配集中。edge.clear();for (int i = 0; i < vertex.size(); i++){if (dirty[i] == true){origin_tag |= ((LLUINT)1 << i);for (int j = 0; j < vertex[i].size(); j++)origin_tag |= ((LLUINT)1 << (vertex[i][j] - 1));}if (dirty[i] == false && vertex[i].size() > 0){LLUINT t = ((LLUINT)1 << i);for (int j = 0; j < vertex[i].size(); j++)t |= ((LLUINT)1 << (vertex[i][j] - 1));edge.push_back(t);}}// 排序,度數大的點首先考慮。sort(edge.begin(), edge.end(), cmp);minimum = 0;finish = false;enumerating_subset();return (base + minimum);}int servicing_stations(vector < vector < int > > &vertex){// 使用寬度優先搜尋分離子圖,計運算元圖的最小支配集頂點個數,相// 加即為原圖的最小支配集頂點個數。int total = 0;while (vertex.size() > 0){vector < vector < int > > open;set < int > close;int size = 0;open.push_back(vertex[0]);close.insert(vertex[0][0]);vertex.erase(vertex.begin());while (open.size() > size){int origin = size;int current = open.size() - 1;size = open.size();for (int i = origin; i <= current; i++){for (int j = 1; j < open[i].size(); j++){if (close.find(open[i][j]) == close.end()){close.insert(open[i][j]);for (int m = 0; m < vertex.size(); m++){if (vertex[m][0] == open[i][j]){open.push_back(vertex[m]);vertex.erase(vertex.begin() + m);break;}}}}}}// 調整分離出的子圖的序號以便後續操作。vector < vector < int > > tmp;for (int c = 1; c <= MAX_TOWN; c++)if (close.find(c) != close.end()){for (int i = 0; i < open.size(); i++)if (open[i][0] == c)tmp.push_back(open[i]);}for (int i = 0; i < tmp.size(); i++){int current = tmp[i][0];for (int m = 0; m < tmp.size(); m++)for (int n = 1; n < tmp[m].size(); n++)if (tmp[m][n] == current)tmp[m][n] = (i + 1);}for (int i = 0; i < tmp.size(); i++)tmp[i].erase(tmp[i].begin());// 計算該子圖的最小支配集頂點數。total += mdsn(tmp);}return total;}int main(int ac, char *av[]){int n;int m;int x, y;vector < vector < int > > vertex;while (cin >> n >> m, n && m){vertex.clear();vertex.resize(n);// 數組的第一個數存放頂點的序號。for (int i = 0; i < n; i++)vertex[i].push_back((i + 1));// 讀入鎮之間的通路。for (int i = 0; i < m; i++){// 自身串連到自身的通路不添加。cin >> x >> y;if (x != y){vertex[x - 1].push_back(y);vertex[y - 1].push_back(x);}}cout << servicing_stations(vertex) << endl;}return 0;}
轉載自:http://blog.csdn.net/metaphysis/article/details/6601365
版本2
1.225msAC。
題目大意:給出n個點和m個關係,可以在任何一個點放服務站,如果這個點放了服務站,與它相連得點均可以被服務到,問最少放多少個服務站可以使得所有所有點均可以被服務到。
解題思路:思路很簡單,用DFS收索,關鍵就在與剪枝,我的主要最佳化在於兩個地方。
首先按點得序號開始DFS, cur 表示當前訪問得點。
<1>對於每個點,無非就是放與不放(注意這裡不能單純根據這個點有沒有被覆蓋到去判斷該不該放服務站)
<2>第一個剪枝,如果這個點增加服務站之後,被覆蓋得點數沒有增加,就可以確定當前點是不放(與放得情況相同幹嘛要多加一個點)
<3>第二個剪枝,當放得服務站個數大於前面計算的最小值時,剪掉這條路。
<4>第三個剪枝,當出現前面有的點無法被覆蓋得時候,可以終止這條路得收索。
這裡問解釋一下第三個剪枝得情況,比如當前已經訪問到第5個點,而1 這點任然沒有被覆蓋到,而與1 有聯絡得點2,2 < 5, 說明2 已經被訪問過了,說明1 和 2 都沒有放服務站,而後面n個點得放與不放都影響不到1 得覆蓋狀態,所以這條路是無法被滿足的。
我在將一下問對最佳化3的一個小最佳化,如果按正常的思路,需要開一個數組去記錄哪些點放了服務站,然後進行最佳化3得時候對判斷點要去遍曆判斷點的所有有關係的點是否已經考慮過。而我得做法是將點得關係點排序,每次只需將最大的點與當前訪問的點進行比較,大於得話說明這個點還沒被確定。
#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;#define N 40int n, m, Min, rec[N];int g[N][N], son[N];int cmp(const int &a, const int &b){ return a > b;}void DFS(int cur, int cnt, int sum){ if (sum >= Min) // 剪枝2:當放得服務站數量大於前面計算的最小值時。return; if (cnt == n)Min = sum; for (int i = 1; i < cur; i++) //剪枝3:出現已經遍曆的點無法被覆蓋,這條路繼續收索是無用功。if (!rec[i] && g[i][0] < cur) return; DFS(cur + 1, cnt, sum); int k = 0, vis[N]; for (int i = 0; i < son[cur]; i++)if (rec[g[cur][i]] == 0){ vis[k++] = g[cur][i]; rec[g[cur][i]] = 1;} if (!k)// 剪枝1:增加覆蓋點為0。return ; DFS(cur + 1, cnt + k, sum + 1); for (int i = 0; i < k; i++)// 回溯要將點還原,不然答案會錯。rec[vis[i]] = 0;}int main(){ int a, b; while (scanf("%d%d", &n, &m), n + m){// Init;memset(g, 0, sizeof(g));memset(son, 0, sizeof(son));memset(rec, 0, sizeof(rec));Min = n + 1;// Read;for (int i = 0; i < m; i++){ scanf("%d%d", &a, &b); g[a][son[a]++] = b; g[b][son[b]++] = a;}// Handle;for (int i = 1; i <= n; i++){ g[i][son[i]++] = i; sort(g[i], g[i] + son[i], cmp);// 最佳化3的最佳化,具體看解釋。}DFS(1, 0, 0);printf("%d\n", Min); } return 0;}
轉載自:點擊開啟連結