最短路徑樹


一.概述

首先我們先搞清楚什么最短路徑樹,我們這里可以引申三個概念,最短路徑,最短路徑樹,最小生成樹

最短路徑:最短路徑就是指兩點之間的最短距離,通常算法有dij,spfa,floyed

最短路徑樹:概念就是以一個節點為根,然后根節點到其他所有點的距離最短,然后形成了一棵樹,把不必要的邊刪除,其實我們用dij的時候求一個點到其他點的距離的時候就已經會把根節點到其他所有點的最短距離求出來了,只是我們不確定是哪些邊構成的

最小生成樹:沒有根的概念,就是用最小的花費保證所有點直接或者間接聯通

這里我來盜用大佬的圖好理解一下

原圖:

最短路徑樹                                                                                                             最小生成樹

 

 

 

二.做法

我們一般可以怎么來寫呢

第一種:我們用dij求出最短路徑,我們已經知道建造整顆最短路徑樹的花費,然后我們去枚舉每一條邊,假設把這條邊刪掉,如果最小花費有影響,說明這個肯定是最短路徑樹上的邊,相等的情況要處理一下,復雜度是O(nlogn*nlogn)

第二種:顯然第一種復雜度太高了,一點都不現實,所以我們一般使用第二種方法,我們還是一樣先求一遍dij,然后我們知道根節點到其他點的距離,我們dfs一遍,如果dis[v]=dis[u]+w,說明這條邊可以充當最短路徑樹上的邊,換上並且沒有影響,或者我們直接枚舉每一條邊,判斷這個條件也可以,看題目不同的需求下要處理什么

 

三.例題鞏固

Description
n個城市用m條雙向公路連接,使得任意兩個城市都能直接或間接地連通。其中城市編號為1..n,公路編號為1..m。
任意個兩個城市間的貨物運輸會選擇最短路徑,把這n*(n-1)條最短路徑的和記為S。現在你來尋找關鍵公路r,公
路r必須滿足:當r堵塞之后,S的值會變大(如果r堵塞后使得城市u和v不可達,則S為無窮大)。

Input
第1行包含兩個整數n,m,
接下來的m行,每行用三個整數描述一條公路a,b,len(1<=a,b<=n),
表示城市a和城市b之間的公路長度為len,這些公路依次編號為1..m。
n<=100,1<=m<=3000,1<=len<=10000。

Output
從小到大輸出關鍵公路的編號。

Sample Input
4 6
1 2 1
2 3 1
3 4 1
1 4 1
1 3 1
4 1 1
Sample Output
1
2
3
5

 

題意:這題很明顯就是要求分別以n個節點為根時的最短路徑樹上的邊不同的條數

思路:這個題我們使用第一種和第二種都可以,如果時上面的邊我們vis數組標記一下,最后統一計數

 

 

[CH6202]黑暗城堡

題意:一個圖,讓你求以1為源點,所能求出的最短路徑樹有多少種不同的構造方法

思路:因為我們要保證到每個點的距離最小,所以我們其實只能用相同的權值替換當前最短路徑樹上的邊,然后運用乘法原理相乘即可

 

BZOJ 4016

題目:https://vjudge.net/contest/307753#problem/F

題意:給你一個圖,讓你建一個最短路徑字典序最小的樹,然后在最短路徑樹上找節點數正好為k的最長路徑長度,並且記錄這樣的路徑數有多少條

思路:我們首先dij跑一遍,然后dfs按點從小到大就能跑出樹來,然后其實樹上路徑操作就很明顯是點分治了,我們在計算距離的時候我們就可以記錄最長路徑,相等則記錄個數,有點樹形DP模板的意思,詳解看這篇點分治的講解    https://www.cnblogs.com/Lis-/p/11359366.html

 

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream> 
#include<vector>
#include<queue>
#define maxn 100005
#define mod 0x3f3f3f3f
using namespace std;
typedef long long ll;
ll da;
vector<pair<ll,ll> > mp[maxn],xx[maxn];//存下圖 
pair<ll,ll> e[maxn],e2[maxn],flag[maxn];
bool vis[maxn];//標記曾經使用過的重心 
ll maxsize[maxn],dis[maxn],d[maxn];//maxsize 當前節點的最大子樹 
ll siz[maxn],xd[maxn];// dis 到重心的距離  d 出現過的距離 
ll n,m,k,rt,sum,qe,qe2,ans1,ans2;  // siz 當前節點的子樹個數  e 出現的距離  rt代表當前重心 
void find(ll x,ll f){//找出重心 
    siz[x]=1;
    maxsize[x]=0;
    for(int i=0;i<mp[x].size();i++){
        pair<ll,ll> q=mp[x][i];
        if(q.first==f||vis[q.first]) continue;//vis數組標記曾經使用過的重心 
        find(q.first,x);
        siz[x]+=siz[q.first];
        maxsize[x]=max(maxsize[x],siz[q.first]); 
    } 
    maxsize[x]=max(maxsize[x],sum-siz[x]);//節點總數減去當前的子樹數=以當前節點為根的父親點子樹數 
    if(maxsize[x]<maxsize[rt]){
        rt=x;
    } 
}
void query(ll z,ll sm){
    if(z>ans1){
        ans1=z;
        ans2=sm;
    }
    else if(z==ans1){
        ans2+=sm;
    }
}
void get_dis(ll x,ll f,ll len,ll num){
    e[qe].first=len;
    e[qe++].second=num; 
    e2[qe2].first=len;
    e2[qe2++].second=num;
    
    if(num==k){
        query(len,1);
    }
    ll t=k-num+1;
    if(flag[t].second){
        query(flag[t].first+len,flag[t].second);
    }
    for(int i=0;i<mp[x].size();i++){
        pair<ll,ll> q=mp[x][i];
        if(q.first==f||vis[q.first]) continue;
        dis[q.first]=dis[x]+len;
        get_dis(q.first,x,len+q.second,num+1);
    }    
}
void divide(ll x){
    vis[x]=1;
    //printf("rt=%lld ans1=%lld ans2=%lld\n",x,ans1,ans2);
    qe2=0;
    for(int i=0;i<mp[x].size();i++){
        pair<ll,ll> q=mp[x][i];
        if(vis[q.first]) continue;
        qe=0;
        dis[x]=q.second;
        get_dis(q.first,x,q.second,2);
        for(int j=0;j<qe;j++){
            pair<ll,ll> w=e[j];
            if(flag[w.second].first<w.first){
                flag[w.second].first=w.first;
                flag[w.second].second=1;
            }
            else if(flag[w.second].first==w.first){
                flag[w.second].second++;
            }
        }
    }
    for(int i=0;i<qe2;i++){
        pair<ll,ll> zz=e2[i];
        flag[zz.second]=make_pair(0,0);
    }
    for(int i=0;i<mp[x].size();i++){
        pair<ll,ll> q=mp[x][i];
        if(vis[q.first]) continue;
        //if(da>0) break;
        sum=siz[q.first];
        rt=0;
        maxsize[rt]=mod;
        find(q.first,x);
        divide(rt);
    }
//    vis[x]=0;
}
void init(){
    ans1=0;ans2=0;
    for(int i=0;i<=n;i++) mp[i].clear();
    for(int i=0;i<=n;i++) xx[i].clear(); 
    for(int i=0;i<=n;i++) vis[i]=0;
    for(int i=0;i<=n;i++) xd[i]=mod;
    for(int i=0;i<=n;i++){
        flag[i]=make_pair(0,0);
    }
} 

void Dijkstra()
{
    xd[1] = 0;
    priority_queue<pair<ll,ll> > Q;
    //priority_queue<pii,vector<pii>,greater<pii> > Q;
    Q.push({xd[1],1});
    while(!Q.empty())
    {
        ll now = Q.top().second;
        Q.pop(); if(vis[now]) continue;
        vis[now] = 1;
        for(int j = 0; j < xx[now].size(); j++)
        {
            ll v = xx[now][j].first;
            if(xd[v] > xd[now]+xx[now][j].second)
            {
                xd[v] = xd[now]+xx[now][j].second;
                Q.push({-xd[v],v});
            }
        }
    }
}
void dfsx(ll x){
    vis[x]=1;
    for(int i=0;i<xx[x].size();i++){
        pair<ll,ll> qq=xx[x][i];
        if(vis[qq.first]||xd[qq.first]!=xd[x]+qq.second) continue;
        mp[x].push_back(make_pair(qq.first,qq.second));
        mp[qq.first].push_back(make_pair(x,qq.second));
        dfsx(qq.first);
    }
}
int main(){
    while(scanf("%lld%lld%lld",&n,&m,&k)!=EOF)
    {
        //if(n==0) break;
        ll a,b,c;
        init();
        for(int i=1;i<=m;i++){
            scanf("%lld%lld%lld",&a,&b,&c);
            xx[a].push_back(make_pair(b,c));
            xx[b].push_back(make_pair(a,c)); 
        }    
        Dijkstra();
        for(int i=0;i<=n;i++) vis[i]=0;
        dfsx(1);
        for(int i=0;i<=n;i++) vis[i]=0;
        sum=n;//當前節點數 
        rt=0;
        maxsize[0]=mod;//置初值 
        find(1,0);
        divide(rt);
        printf("%lld %lld\n",ans1,ans2);
    
    }
} 
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM