1. 程式人生 > >UVA10600 次小生成樹

UVA10600 次小生成樹

題目連結:https://vjudge.net/problem/UVA-10600

題意:叫我們求出最小生成樹的邊權之和 和次小生成樹的邊權之和。

思路:我們可以先求出最小生成樹,這個不難,如果要求次小生成樹,那麼我們肯定是要在最小生成樹裡面去掉一條邊(假設是a),這樣就變成兩顆生成樹了,我們就還要找一條邊(假設是b)把這兩顆樹連線。這裡我們需要滿足的就是b-a最小(b一定大於a),這樣找到的生成樹就是次小生成樹了是吧。

關鍵就是怎麼更快的找到b和a這兩條邊的權值,使b-a最小。因為現在已經是最小生成樹了,我們如果連線任意兩點,那麼根據樹的性質,就一定會形成一個環了,那麼我們肯定是去掉這個環裡面除去邊b之外的所有邊裡面權值最大的邊,這樣才可以在選擇連線邊b的情況下使重新生成的樹的權值最小。然按照這個思路我們列舉每兩點,就可以求出次小生成樹了。

現在我們就要求出在最小生成樹上每兩點之間最大的邊權,我們在這裡用動態規劃來實現,用maxx[i][j]來記錄點i和點j之間最大的邊權,在用prime演算法來求最小生成樹的時候,我們每增加一個點到樹中,就求出這個點和已經加入樹裡面的所有點之間的maxx陣列值,這裡還要記錄每個點的前驅,(假設點u的前驅就是pre[u],那麼pre[u]就是在生成樹裡面和點u直接相連的並且在點u之前加入樹中的點),那麼假設現在我把點u加到樹上,點j在點u之前就已經加在樹上了,現在我要求出maxx[j][u],遞推公式就是

maxx[j][u]=max(maxx[j][pre[u]],map[u][pre[u]]);也就是max(pre[u]和點j的maxx陣列值,pre[u]和點u直接相連的邊的權值);

看程式碼:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<set>
#include<cstdio>
#include<string>
#include<deque> 
using namespace std;
typedef long long
LL; #define eps 1e-8 #define INF 0xffffff #define maxn 105 int n,m,k,t,ans1,ans2,sum; int check[maxn][maxn],map[maxn][maxn],pre[maxn],vis[maxn],maxx[maxn][maxn],dis[maxn]; //check[i][j]表示最小生成樹裡i和j是否直接相連,map[i][j]記錄i和j的邊權 //pre[u]記錄點u的前驅,maxx[i][j]記錄點i和點j之間路徑上權值最大的邊 void init(){ memset(check,0,sizeof(check)); memset(pre,0,sizeof(pre)); memset(vis,0,sizeof(vis)); memset(maxx,0,sizeof(maxx)); for(int i=1;i<n;i++){ for(int j=i+1;j<=n;j++){ map[i][j]=map[j][i]=INF; } } sum=0;//記錄最小生成樹的邊權和 ans1=ans2=INF; } void prime(){ for(int i=1;i<=n;i++) dis[i]=INF; dis[1]=0; for(int i=1;i<=n;i++){ int u,min1=INF; for(int j=1;j<=n;j++){ if(!vis[j]&&dis[j]<min1){ min1=dis[j]; u=j; } } vis[u]=1; sum+=min1; check[pre[u]][u]=check[u][pre[u]]=1;//把走過的邊標記 for(int j=1;j<=n;j++){ if(vis[j]&&j!=u){//用動態規劃來計算點u和點j的maxx陣列值 maxx[j][u]=maxx[u][j]=max(maxx[pre[u]][j],map[pre[u]][u]); } if(!vis[j]&&dis[j]>map[u][j]){ dis[j]=map[u][j]; pre[j]=u; } } } } void second(){//求次小生成樹的權值和 for(int i=1;i<n;i++){ for(int j=i+1;j<=n;j++){ if(map[i][j]!=INF&&check[i][j]==0){ ans2=min(ans2,sum+map[i][j]-maxx[i][j]);//去掉一條邊,增加一條邊 } } } } int main() { scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); init(); int u,v,w; for(int i=1;i<=m;i++){ scanf("%d%d%d",&u,&v,&w); map[u][v]=map[v][u]=min(map[u][v],w); } prime(); ans1=sum; second(); printf("%d %d\n",ans1,ans2); } return 0; }