1. 程式人生 > 實用技巧 >校內測T2 李白坐地鐵(Dijkstra模板題)

校內測T2 李白坐地鐵(Dijkstra模板題)

題目背景

濟南地鐵已經開通了R1和R3線。

小李白也興奮地在高考前去體驗了一下,發現可以用公交卡刷卡並且車站文化氛圍很濃厚qwqqwq

題目描述

小李白來到了一個大城市,他住在了一個五星級酒店,他規劃著接下來的幾天內在大城市的城區內坐地鐵遊玩景點。這個城市有著完善的地鐵網路。但是非常不幸的是,地鐵全是單向行駛的。

小李白每天都要坐地鐵去一個景點遊玩,然後傍晚時分坐地鐵回來。第一天去編號為2的景點,第二天去編號為3的景點......小李白要去過所有的景點才會罷休qwqqwq。我們設每一個地鐵站點都是一個景點。對於每一天的景點,小李白都想知道如何坐地鐵才能使得來回總時間合最小。他會給你整個城市的地鐵路線和站點圖,並且向你求助這個問題。

nn個站點,mm個站點間的地鐵線路。小李白所住的酒店需要耗時時間dd才能到地鐵站11,也就是從站點11開始坐地鐵。

資料會給你每對站點之間的路線長度ww.

輸入格式

第1行:n,m,dn,m,d

2到m+12m+1行,u,v,w,表示從站點u到站點v有一條長度為w的地鐵路線。

輸出格式

小李白每天在路上耗時的和sumsum。 為了更方便的解釋,我們設每天在路上花費的時間為time[i]time[i],小李白要去的景點數為kk

那麼答案就是\sum_{i=1}^k time[i]i=1ktime[i]

輸入輸出樣例

輸入 #1
5 10 0
2 3 5
1 5 5
3 5 6
1 2 8
1 3 8
5 3 4
4 1 8
4 5 3
3 5 6
5 4 2
輸出 #1
83

說明/提示

對於30\%30%的資料,n=1n=1

對於100\%100%的資料:

n\leqslant1000n1000

m\leqslant100000m100000

w\leqslant200w200

d\leqslant1500000d1500000

均為整數。

小李白遠遠不如他的朋友ych強,所以只能結合高考前放假經歷隨便出了個題

注意空間時間限制qwqqwq

qwq_{ych_{Orz}}^{ybr_{csl}^{tcl}}

思路:

參觀每一個景點都要從一號車站過去再回來,所以很明顯是一個單源最短路,因為沒有負邊權,所以可以使用比SPFA更快的Dijkstra演算法來求單源最短路。但是從各個景點回來的時候應該怎麼辦呢?通過dalao的題解,我學會了建反邊這一個神奇的操作。因為每次從景點回來都是回到一號車站,都是那一個點,所以如果在另一張新圖裡反向建邊,就可以再跑一遍Dijkstra,從而轉化為單源最短路(妙啊)

程式碼:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int N=1005,M=100005;
long long n,m,k,tot1,tot2,ans;
long long Next1[M],ver1[M],edge1[M],head1[N],d1[N],Next2[M],ver2[M],edge2[M],head2[N],d2[N];//不開longlong見祖宗 
bool v1[N],v2[N];
priority_queue< pair <int,int> >q1;
priority_queue< pair <int,int> >q2;//因為要存兩個圖,所以什麼變數都開兩遍 
void add1(long x,long long y,long long z){
    ver1[++tot1]=y;//編號為tot的邊的終點是y 
    edge1[tot1]=z;//邊權是z 
    head1[x]=tot1;//鏈式前向星結構,把新加入的邊放到與x相連線的第一條邊
    Next1[tot1]=head1[x];//新加入的邊的下一條邊是原本與x相連的第一條邊     
}
void add2(long long x,long long y,long long z){
    ver2[++tot2]=y;
    edge2[tot2]=z;
    head2[x]=tot2;
    Next2[tot2]=head2[x];
}
void dijkstra1(long long x){
    memset(d1,0x3f,sizeof(d1));//把起點到所有點的距離都初始化為無窮大,保證能更新 
    memset(v1,0,sizeof(v1));//一開始所有點都沒有到過 
    d1[x]=0;//自己到自己的距離當然是0 
    q1.push(make_pair(0,x));//pair的第一維表示點x到起點的距離,第二維表示點的編號x 
    while(q1.size()!=0){//當堆不為空 
        long long x=q1.top().second;//取出第二維點的編號 
        q1.pop();//彈出 
        if(v1[x]==1) continue;//如果這個點已經被訪問過了,那麼繼續迴圈 
        v1[x]=1;//如果沒有訪問過那麼標記為訪問過 
        for(long long i=head1[x];i;i=Next1[i]){//鏈式前向星遍歷
            long long y=ver1[i],z=edge1[i];
            if(d1[y]>d1[x]+z){//鬆弛操作 
                d1[y]=d1[x]+z;
                q1.push(make_pair(-d1[y],y));//加入堆中,因為STL中提供的是大根堆,我們要用小根堆,所以把距離的相反數存進去排序,就相當於是小根堆了 
            }
        }
    }
}
void dijkstra2(long long x){
    memset(d2,0x3f,sizeof(d2));
    memset(v2,0,sizeof(v2));
    d2[x]=0;
    q2.push(make_pair(0,x));
    while(q2.size()!=0){
        long long x=q2.top().second;
        q2.pop();
        if(v2[x]==1) continue;
        v2[x]=1;
        for(long long i=head2[x];i;i=Next2[i]){ 
            long long y=ver2[i],z=edge2[i];
            if(d2[y]>d2[x]+z){
                d2[y]=d2[x]+z;
                q2.push(make_pair(-d2[y],y));
            }
        }
    }
}
int main()
{
    scanf("%lld%lld%lld",&n,&m,&k);
    for(long long i=1,x,y,z;i<=m;i++){
        scanf("%lld%lld%lld",&x,&y,&z);
        add1(x,y,z);
        add2(y,x,z);//反著建邊 
    }
     ans=(n-1)*k*2;//每天從酒店到一號車站所要走的距離 
    dijkstra1(1);
    dijkstra2(1);//跑兩遍Dijkstra 
    for(long long i=1;i<=n;i++){
        ans+=d1[i]+d2[i];//把來回的距離和加起來 
    }
    printf("%lld\n",ans);
    return 0;
}