題目1 : 最小生成樹三·堆優化的Prim算法
時間限制:
10000ms
單點時限:
1000ms
內存限制:
256MB
描述
回到兩個星期之前,在成功的使用Kruscal算法解決了問題之后,小Ho產生了一個疑問,究竟這樣的算法在稀疏圖上比Prim優化之處在哪里呢?
提示:沒有無緣無故的優化!輸入
每個測試點(輸入文件)有且僅有一組測試數據。
在一組測試數據中:
第1行為2個整數N、M,表示小Hi擁有的城市數量和小Hi篩選出路線的條數。
接下來的M行,每行描述一條路線,其中第i行為3個整數N1_i, N2_i, V_i,分別表示這條路線的兩個端點和在這條路線上建造道路的費用。
對於100%的數據,滿足N<=10^5, M<=10^6,於任意i滿足1<=N1_i, N2_i<=N, N1_i≠N2_i, 1<=V_i<=10^3.
對於100%的數據,滿足一定存在一種方案,使得任意兩座城市都可以互相到達。
輸出
對於每組測試數據,輸出1個整數Ans,表示為了使任意兩座城市都可以通過所建造的道路互相到達至少需要的建造費用。
- 樣例輸入
-
5 29 1 2 674 2 3 249 3 4 672 4 5 933 1 2 788 3 4 147 2 4 504 3 4 38 1 3 65 3 5 6 1 5 865 1 3 590 1 4 682 2 4 227 2 4 636 1 4 312 1 3 143 2 5 158 2 3 516 3 5 102 1 5 605 1 4 99 4 5 224 2 4 198 3 5 894 1 5 845 3 4 7 2 4 14 1 4 185
- 樣例輸出
-
92
分析:之前弄了好長時間,看了好多材料也沒搞懂怎么優化prim。今天在操作系統課上,不小心走神了。於是乎,只花了幾分鍾就想
出來了到底要怎么優化。之前看別人的博客什么的,感覺很復雜。現在把我的想法寫出來:
堆優化prim算法:首先我們先想,之前在寫prim的時候需要這樣做,從當前的生成樹開始,遍歷所有可以抵達當前生成樹的邊,找到
一條最短的邊,將該邊的權值加到生成樹的權值總和上,加該點標記訪問,並加到生成樹上來。現在如果我們可以優化方法找到那條最
短邊的話,那復雜度不就降低了。怎么優化呢?之前的做法是用一個數組保存每個節點到生成樹的距離,每次找的過程都要遍歷一次這
個數組。現在我們用一個優先隊列(小根堆)來保存所有可以抵達生成樹的邊,每次只要取出該隊列的最前面的且合法的邊加到生成樹
上來就醒了。不合法的邊會在這個過程中丟棄!
有圖有文字的描述過程如下:
代碼:#include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <math.h> #include <iostream> #include <string> #include <stack> #include <vector> #include <set> #include <queue> #include <algorithm> #define LL long long int #define N 100000+10 //最大節點數 #define M 1000000+10 //最大的邊數 #define MOD 142857 //N<=10^5, M<=10^6 using namespace std; int n, m; struct node { int v, w; bool operator<(const node &dd)const{ return w>dd.w; } //權值小的優先 }; vector<node>q[N]; bool vis[N]; //堆優化的prim算法 LL ans; void queue_prim() { //以節點1為起點進行擴展安全邊 生成最小樹 priority_queue<node>que; while(!que.empty()) que.pop(); //初始化清空優先隊列 維護一個小根堆 //這樣每次找安全邊的速度就提高了 ans = 0; memset(vis, false, sizeof(vis)); for(int i=0; i<q[1].size(); i++){ que.push(q[1][i]); //將起點的所有連接邊全部加入隊列中來 } vis[1]=true; int edge=n-1;//邊數 node cur; while(edge--) { cur = que.top(); que.pop();//這個地方需要注意一下 //並不是每個從優先隊列取出來的邊都是可以加到生成樹上去的 if(vis[cur.v]==true){ while(vis[cur.v]){ cur=que.top(); que.pop(); } } ans = ans+cur.w; //printf("%d-- ", cur.w ); vis[cur.v]=true; //加入生成樹的該點將被標記訪問 for(int i=0; i<q[cur.v].size(); i++){ if(vis[ q[cur.v][i].v ]==false) //當前加入生成樹的點可以擴充出的邊指向的節點 que.push(q[cur.v][i]);//如果沒有被訪問才會加入到隊列當中來 } } } int main() { scanf("%d %d", &n, &m); int i, j; int u, v, w; node cur; for(i=0; i<=n; i++) q[i].clear(); for(i=0; i<m; i++) { scanf("%d %d %d", &u, &v, &w); cur.v=v; cur.w=w; q[u].push_back(cur); cur.v=u; q[v].push_back(cur); //建立雙向邊 } queue_prim(); printf("%lld\n", ans ); return 0; }