1. 程式人生 > >次小生成樹題目及思路 poj1679

次小生成樹題目及思路 poj1679

下面只是講講我對次小生成樹一些理解,推薦先去看一下次小生成樹的一些概念再來看這篇部落格。

首先我們要明確一點,在最小生成樹中,任意兩個點之間有且僅有一條路徑。然後是次小生成樹由何而來,必然是最小生成樹轉變而來。如何轉變,應該是將一條不是最小生成樹中的邊,替換最小生成樹的一條邊,且替換後任意節點之間還應該有通路。那麼思路就來了。由於最小生成樹的特點,因此往樹中新增一條邊,就必然會形成一個環,那麼去掉的邊也應該是環上屬於最小生成樹的某一條邊。大體思路就是這個樣子。

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; }