BZOJ1977 AcWing 1148. 祕密的牛奶運輸(次小生成樹)

題幹:

農夫約翰要把他的牛奶運輸到各個銷售點。
運輸過程中,可以先把牛奶運輸到一些銷售點,再由這些銷售點分別運輸到其他銷售點。
運輸的總距離越小,運輸的成本也就越低。
低成本的運輸是農夫約翰所希望的。
不過,他並不想讓他的競爭對手知道他具體的運輸方案,所以他希望採用費用第二小的運輸方案而不是最小的。
現在請你幫忙找到該運輸方案。
注意::
如果兩個方案至少有一條邊不同,則我們認爲是不同方案;
費用第二小的方案在數值上一定要嚴格小於費用最小的方案;
答案保證一定有解;
輸入格式
第一行是兩個整數 N,M,表示銷售點數和交通線路數;
接下來 MM 行每行 3 個整數 x,y,z,表示銷售點 x 和銷售點 y 之間存在線路,長度爲 z。
輸出格式
輸出費用第二小的運輸方案的運輸總距離。

數據範圍
1N500,1≤N≤500,
1M104,1≤M≤10^4,
1z109,1≤z≤10^9,
數據中可能包含重邊。
輸入樣例:
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章