次小生成樹題目及思路 poj1679
阿新 • • 發佈:2019-01-05
下面只是講講我對次小生成樹一些理解,推薦先去看一下次小生成樹的一些概念再來看這篇部落格。
首先我們要明確一點,在最小生成樹中,任意兩個點之間有且僅有一條路徑。然後是次小生成樹由何而來,必然是最小生成樹轉變而來。如何轉變,應該是將一條不是最小生成樹中的邊,替換最小生成樹的一條邊,且替換後任意節點之間還應該有通路。那麼思路就來了。由於最小生成樹的特點,因此往樹中新增一條邊,就必然會形成一個環,那麼去掉的邊也應該是環上屬於最小生成樹的某一條邊。大體思路就是這個樣子。
1.先形成最小生成樹,同時對於需要標記屬於最小生成樹的邊,下面程式碼用vis來標記。
2.在每次選取一個離生成樹最近的一個點後,需要列舉所有已經加入最小生成樹的點,依次更新Max陣列,Max[i][j]表示在i節點和j節點的路徑上最小生成樹的最大權值(因為在最後列舉不是最小生成樹的邊的時候,顯然去掉環上權值最大的邊才能使次小生成樹的總值儘可能的小)。而Max[i][j]的更新是dp的思想,建議好好理解。
3.最後就是列舉所有不在最小生成樹上的邊,依次更新次小生成樹。
下面的程式碼是poj1679的題。
就是詢問所給出的資料所形成的最小生成樹是否為一。
如何判斷唯一,只需要判斷次小生成樹是否和最小生成樹相等就行了。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int maxn = 105;
int e[maxn][maxn];//圖
int vis[maxn][maxn];//標記邊
int dis[maxn];//原本是距離陣列,但是因為是求最小生成樹,因此也可以
int pre[maxn];//前驅節點
int Max[maxn][maxn];//記錄最大權值的邊
int t, n, m;
int sum;//最小生成樹
int minsum;//次小生成樹
//bool cmp(pii a,pii b){
//return a.second<b.second;
//}
void prim() {
priority_queue<pii, vector <pii>, greater<pii> >q;
//這裡求大佬解答,一開始過載了pair的小於號,就是上面所註釋掉的語句,但是好像沒效果
dis[1] = 0;
q.push(pii(0, 1));//距離,節點
while(!q.empty()) {
int u = q.top().first;//u是距離
int v = q.top().second;//v是節點
q.pop();
if(dis[v] < u) continue;//如果當前節點是樹中節點就返回
sum += dis[v];
dis[v] = -1;//一旦已經是樹中集合,就記為-1
if(pre[v] > 0) {
vis[v][pre[v]] = vis[pre[v]][v] = 1;//pre[i]=j,表示i的前驅節點是j
}
for(int i = 1; i <= n; i++) {
if(i == v) continue; //不知道為什麼不可以相等
if(dis[i] < 0) { //如果已經是樹中節點就要更新Max
Max[i][v] = Max[v][i] = max(Max[pre[v]][i], e[v][pre[v]]);//這裡有dp的思想,是核心
} else if(dis[i] > e[i][v]) {//普通的prim更新操作,多了pre陣列的更新
dis[i] = e[i][v];
pre[i] = v;
q.push(pii(dis[i], i));
}
}
}
}
void solve() {
minsum = inf;
for(int i = 1; i <= n; i++) {//遍歷每一條不在最小生成樹的邊
for(int j = 1 + 1; j <= n; j++) { //因為是雙向圖,所以只要遍歷一半圖就行了
if(!vis[i][j]) {//如果不在就更新minsum
minsum = min(minsum, sum - Max[i][j] + e[i][j]);
}
}
}
if(minsum == sum) {
printf("Not Unique!\n");
} else printf("%d\n", sum);
}
int main() {
scanf("%d", &t);
while(t--) {
sum = 0;
memset(dis, inf, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(pre, -1, sizeof(pre));
memset(e, inf, sizeof(e));
memset(Max, 0, sizeof(Max));
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
e[u][v] = w;//雙向
e[v][u] = w;
}
prim();
solve();
}
return 0;
}