題幹:
農夫約翰要把他的牛奶運輸到各個銷售點。
運輸過程中,可以先把牛奶運輸到一些銷售點,再由這些銷售點分別運輸到其他銷售點。
運輸的總距離越小,運輸的成本也就越低。
低成本的運輸是農夫約翰所希望的。
不過,他並不想讓他的競爭對手知道他具體的運輸方案,所以他希望採用費用第二小的運輸方案而不是最小的。
現在請你幫忙找到該運輸方案。
注意::
如果兩個方案至少有一條邊不同,則我們認爲是不同方案;
費用第二小的方案在數值上一定要嚴格小於費用最小的方案;
答案保證一定有解;
輸入格式
第一行是兩個整數 N,M,表示銷售點數和交通線路數;
接下來 MM 行每行 3 個整數 x,y,z,表示銷售點 x 和銷售點 y 之間存在線路,長度爲 z。
輸出格式
輸出費用第二小的運輸方案的運輸總距離。
數據範圍
數據中可能包含重邊。
輸入樣例:
4 4
1 2 100
2 4 200
2 3 250
3 4 100
輸出樣例:
450
思路:
題幹很直白的告訴了我們要求一個次小生成樹,也就是說和最小生成樹只有一條邊不同。
將加入最小生成樹的邊稱爲樹邊,其餘邊稱爲非樹邊,則就是枚舉選擇一條樹邊去除,選擇一條非樹邊加入。
當我們選擇一條非樹邊(x,y,z)加入最小生成樹後,肯定形成環(因爲最小生成樹已經是極小連通子圖),則在形成的環中找最大邊權mx1,嚴格次大邊權mx2(mx1>mx2)
①z>mx1:換掉最大邊權(因爲是次小,所以只能換最大的)
②z=mx1:換掉嚴格次大邊權(因爲是次小,最大的邊權又相等,所以換嚴格次大)
枚舉所有非樹邊,這樣最後得到的就是次小生成樹。
現在問題轉換成了求一條路徑上的最大邊權和嚴格次大邊權
(1)樹上倍增
(2)我們將樹邊新建一個圖(注意是按原點建圖,不是按並查集之後的父點),然後劃分連通塊,這樣一個連通塊就代表一個非樹邊加入後的環,然後在當前連通塊找最大邊權和嚴格次大邊權。
dfs劃分過程中記錄最大值和次大值就行,每個點出發對應的值用數組存儲
//2、dfs劃分連通塊
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
typedef long long ll;
struct stu{
int x,y,w;
bool f;
bool operator< (const stu &t) const
{
return w < t.w;
}
}edge[10100];
struct E{
int next,w;
};
int F[10100],dis1[510][510],dis2[510][510];
vector<E> tu[510];
int find(int i){
if(F[i]==i)
return i;
return F[i]=find(F[i]);
}
void add(int a,int b,int c){ //建立新圖
E t;
t.next=b;
t.w=c;
tu[a].push_back(t);
}
void dfs(int u,int fa,int mx1,int mx2,int d1[],int d2[]){//劃分連通塊並找兩個值
d1[u]=mx1;
d2[u]=mx2;
for(int i=0;i<tu[u].size();i++){
int v=tu[u][i].next,w=tu[u][i].w;
int td1=mx1,td2=mx2;
if(v!=fa){
if(w>td1){
td2=td1;
td1=w;
}
else if(w>td2){
td2=w;
}
dfs(v,u,td1,td2,d1,d2);
}
}
}
int main(){
int n,m,a,b,c;
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++){
scanf("%d%d%d",&a,&b,&c);
edge[i]={a,b,c};
}
sort(edge,edge+m);
for(int i=0;i<=n;i++)
F[i]=i;
ll ans=0;
for(int i=0;i<m;i++){
a=find(edge[i].x);b=find(edge[i].y);
c=edge[i].w;
//printf("%d %d %d\n",a,b,c);
if(a!=b){
ans+=c;
F[a]=b;
edge[i].f=true; //記錄樹邊
add(edge[i].x,edge[i].y,c);
add(edge[i].y,edge[i].x,c);
}
}
//printf("%d\n",ans);
for(int i=1;i<=n;i++){ //從點i出發
dfs(i,-1,0,0,dis1[i],dis2[i]);
}
ll res=1e18;
for(int i=0;i<m;i++){ //遍歷非樹邊
if(!edge[i].f){
a=edge[i].x,b=edge[i].y,c=edge[i].w;
ll t=1e18;
if(c>dis1[a][b]){ //判斷是否可以加入
t=ans+c-dis1[a][b];
}
else if(c>dis2[a][b]){
t=ans+c-dis2[a][b];
}
res=min(res,t);
}
}
printf("%lld\n",res);
return 0;
}