連結:
http://poj.org/problem?id=3164
題目:
Command Network
| Time Limit: 1000MS |
|
Memory Limit: 131072K |
| Total Submissions: 8922 |
|
Accepted: 2609 |
Description After a long lasting war on words, a war on arms finally breaks out between littleken’s and KnuthOcean’s kingdoms. A sudden and violent assault by KnuthOcean’s force has rendered a total failure of littleken’s command network. A provisional network must be built immediately. littleken orders snoopy to take charge of the project. With the situation studied to every detail, snoopy believes that the most urgent point is to enable littenken’s commands to reach every disconnected node in the destroyed network and decides on a plan to build a unidirectional communication network. The nodes are distributed on a plane. If littleken’s commands are to be able to be delivered directly from a node A to another node B, a wire will have to be built along the straight line segment connecting the two nodes. Since it’s in wartime, not between all pairs of nodes can wires be built. snoopy wants the plan to require the shortest total length of wires so that the construction can be done very soon. Input The input contains several test cases. Each test case starts with a line containing two integer N (N ≤ 100), the number of nodes in the destroyed network, and M (M ≤ 104), the number of pairs of nodes between which a wire can be built. The next N lines each contain an ordered pair xi and yi, giving the Cartesian coordinates of the nodes. Then follow M lines each containing two integers i and j between 1 and N(inclusive) meaning a wire can be built between node i and node j for unidirectional command delivery from the former to the latter. littleken’s headquarter is always located at node 1. Process to end of file. Output For each test case, output exactly one line containing the shortest total length of wires to two digits past the decimal point. In the cases that such a network does not exist, just output ‘poor snoopy’. Sample Input 4 60 64 60 07 201 21 32 33 43 13 24 30 01 00 11 21 34 12 3 Sample Output 31.19poor snoopy Source POJ Monthly--2006.12.31, galaxy |
分析與總結:
1. 沒注意讀題,把unidirectional看成了undirectional, 一個字母之差,意思卻天壤之別,Orz...(PS: unidirectional 單向的)
2. 知道了是單向之後,還是很天真的認為就是最小產生樹,用了prim演算法,結果WA了
3. 本題是最小樹形圖,解決這個問題的演算法是朱永津與劉振宏在上世紀60年代解決的(朱劉演算法),終於遇到個中國人命名的演算法了 = =
在網上找了幾分資料,但感覺對於自環收縮這個最小樹形圖最核心的部分都講得不夠清楚,看得我真是一頭霧水啊,
百度百科那個過程圖,對於初學者來說又過於複雜了。
最終還是看了這個大神的部落格才真正的懂了,講解得通俗易懂,過程圖也很贊,看了就懂,大愛啊
http://hi.baidu.com/lydrainbowcat/item/5fbae3fb9c159c5ec8f33753
以下轉自該部落格:
題目大意:給定一個有向圖,根節點已知,求該有向圖的最小樹形圖。最小樹形圖即有向圖的最小產生樹,定義為:選擇一些邊,使得根節點能夠到達圖中所有的節點,並使得選出的邊的邊權和最小。
題目演算法:朱-劉演算法(即由中國人朱永津和劉振宏共同發明的演算法)。
演算法步驟如下:(本文不再證明,參考下面給出的我自己畫的一個圖即可理解)
1.判斷圖的連通性,若不連通直接無解,否則一定有解。
2.為除了根節點以外的所有點選擇一個權值最小的入邊,假設用pre數組記錄前驅,f數組記錄選擇的邊長,記所選邊權和為temp。
3.(可利用並查集)判斷選擇的的邊是否構成環,若沒有則直接ans+=temp並輸出ans,若有,則進行下一步操作。
4.對該環實施縮點操作,設該環上有點V1,V2……Vi……Vn,縮成的點為node ,對於所有不在環中的點P進行如下更改:
(1) 點P到node的距離為min{a[p,Vi]-f[Vi]} (a為邊集數組)
(2)點node到p的距離為min{a[Vi,p]}
操作(1)的理解:先假設環上所有邊均選上,若下次選擇某一條邊進入該環,則可以斷開進入點與進入點的前驅之間的邊,即斷開F[進入點],所以等效為直接把a[p,node]賦值為min{a[p,Vi]-f[Vi]}。
特別提醒:本題有自環,可以提前刪掉,因為它沒有用。
除了這道模板題,有還有模板題:
UVa 11183 - Teen Girl Squad
TJU 2248 Channel Design
用鄰接矩陣實現的代碼:
#include<cstdio>#include<iostream>#include<cstring>#include<cmath>using namespace std;const int VN = 105;const int INF = 0x7fffffff;template<typename Type>class Directed_MST{public: void init(int _n){ n=_n; ans = 0; memset(vis, 0, sizeof(vis)); memset(inc, 0, sizeof(inc)); for(int i=0; i<=n; ++i){ w[i][i] = INF; for(int j=i+1; j<=n; ++j) w[i][j]=w[j][i]=INF; } } void insert(int u, int v, Type _w){ if(w[u][v]>_w) w[u][v] = _w; } Type directed_mst(int u){ //== 步驟1: 判斷能否形成最小樹形圖,直接dfs遍曆 dfs(u); for(int i=1; i<=n; ++i) if(!vis[i]) { return -1; } //== 如果可以形成最小樹形圖,繼續 memset(vis, 0, sizeof(vis)); while(true){ //== 1. 找最小前驅邊 for(int i=1; i<=n; ++i)if(i!=u&&!inc[i]){ w[i][i]=INF, pre[i] = i; for(int j=1; j<=n; ++j)if(!inc[j] && w[j][i]<w[pre[i]][i]){ pre[i] = j; } } //== 2.判斷是否有環 int i; for(i=1; i<=n; ++i)if(i!=u&&!inc[i]){ int j=i, cnt=0; while(j!=u && pre[j]!=i && cnt<=n) j=pre[j], ++cnt; if(j==u || cnt>n) continue; //沒找到 break; } //== 沒有找到環,得到答案 if(i>n){ for(int i=1; i<=n; ++i)if(i!=u && !inc[i]) ans+=w[pre[i]][i]; return ans; } //== 有環,進行收縮 int j=i; memset(vis, 0, sizeof(vis)); do{ ans += w[pre[j]][j], j=pre[j], vis[j]=inc[j]=true; }while(j!=i); inc[i] = false; // 環縮成了點i,點i仍然存在 //== 收縮 for(int k=1; k<=n; ++k)if(vis[k]){ // 在環中點點 for(int j=1; j<=n; ++j)if(!vis[j]){ // 不在環中的點 if(w[i][j] > w[k][j]) w[i][j] = w[k][j]; if(w[j][k]<INF && w[j][k]-w[pre[k]][k] < w[j][i]) w[j][i] = w[j][k] - w[pre[k]][k]; } } } return ans; }private: // 從根結點遍曆一遍,判斷是否存在最小樹形圖 void dfs(int u){ vis[u] = true; for(int i=1; i<=n; ++i)if(!vis[i]&&w[u][i]<INF){ dfs(i); } }private: Type ans; // 所求答案 int n; // 結點個數 int pre[VN]; // 權值最小的前驅邊 bool vis[VN]; // 是在環中還是在環外 bool inc[VN]; // 該點是否被刪除了(收縮) Type w[VN][VN]; // 圖};Directed_MST<double>G;double x[VN],y[VN];inline double getDist(double x1,double y1,double x2,double y2){ return sqrt(pow(x1-x2,2)+pow(y1-y2,2));}int main(){ int n,m; while(~scanf("%d%d",&n,&m)){ G.init(n); for(int i=1; i<=n; ++i) scanf("%lf%lf",&x[i],&y[i]); for(int i=0; i<m; ++i){ int a,b; scanf("%d%d",&a,&b); if(a==b)continue; G.insert(a,b,getDist(x[a],y[a],x[b],y[b])); } double ans = G.directed_mst(1); if(ans < 0) puts("poor snoopy"); else printf("%.2f\n", ans); } return 0;}
—— 生命的意義,在於賦予它意義。
原創 http://blog.csdn.net/shuangde800 , By D_Double (轉載請標明)