luoguP4568 [JLOI2011]飛行路線(分層圖最短路模版題)

問題描述

Alice 和 Bob 現在要乘飛機旅行,他們選擇了一家相對便宜的航空公司。該航空公司一共在nn個城市設有業務,設這些城市分別標記爲 0 到 n−1,一共有 m 種航線,每種航線連接兩個城市,並且航線有一定的價格。
Alice 和 Bob 現在要從一個城市沿着航線到達另一個城市,途中可以進行轉機。航空公司對他們這次旅行也推出優惠,他們可以免費在最多 k 種航線上搭乘飛機。那麼 Alice 和 Bob 這次出行最少花費多少?

Input

數據的第一行有三個整數,n,m,k,分別表示城市數,航線數和免費乘坐次數。

第二行有兩個整數,s,t,分別表示他們出行的起點城市編號和終點城市編號。

接下來有 m 行,每行三個整數,a,b,c,表示存在一種航線,能從城市 a 到達城市 b,或從城市 b 到達城市 a,價格爲 c。

Output

輸出一行一個整數,爲最少花費。

Sample input

5 6 1
0 4
0 1 5
1 2 5
2 3 5
3 4 5
2 3 3
0 2 100

Sample output

8

數據規模與約定

2n104,1m5×104,0k10,0s,t,a,bn,a!=b,0c1032\le n\le 10^4,1\le m \le 5\times 10^4,0\le k \le 10,0\le s,t,a,b\le n,a!=b,0\le c\le 10^3

解題思路

有k次免費飛行的機會,也就意味着有k條路徑權值爲0,這是一個分層圖最短路模版題。

那分層圖怎麼用呢?

先看看分兩層的圖,我們可以做圖如下:
在這裏插入圖片描述
圖中,上面一層是原本的圖,下面一層和上層數據相同,紅線表示兩者之間有路線,重點是藍線和綠線。藍線是上一層到下一層到路線,比如,我們可以通過紅線從本層的1號到本層的2號,那麼我們就可以通過藍線從本層的1號到下一層的2號,藍線代表的權值是另外一條路線。在本題中,所有藍線權值都是0,就是免費線路。而綠線是上一層終點到下一層終點的路線,賦值爲0,作用等下再說。

我們不難發現,這個圖做出來後,直接用dijkstra來計算就可以了,在這個圖中,我們只能從本層中運動(通過紅線),或者從上層到下層(通過藍線或者綠線),每下降一層,就意味着用了一次特殊的通道,因此我們要分k+1層圖。最終我們要計算的結果是從起始點s,到最後一層的終點t。

可能有人有疑問,如果不用k條特殊路線到達終點的路線更短呢?我們的綠線就起作用了,如果你只使用了m條特殊路線,你最終就會到達第m+1層地圖,而所有層的終點都是通過一條權值爲0的路線連接的,所以如果你到第m+1層停下了,那麼這個結果和從s到第k+1層停下一樣。

分層圖是一種概念上的分層,在存儲時,假設第一層各個結點是1n1\sim n,那麼第2層就是n+12nn+1\sim 2n,依次類推。更多詳細內容見下方代碼。

完整代碼

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;

const int maxn=5000000+10;//數據範圍... ...無語,以後我開到百萬級別的
struct node
{
    int to,w,next;
};
node edge[maxn];
int head[maxn],dis[maxn];
int len,n,m,k,s,t;
bool visit[maxn];

void add(int x,int to,int w);
void dijkstra(int s);

int main()
{
    cin>>n>>m>>k;
    cin>>s>>t;//s->t
    for (int i=1; i<=m; i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
        for(int j=1;j<=k;++j)
        {
            add(a+(j-1)*n,b+j*n,0);
            add(b+(j-1)*n,a+j*n,0);
            add(a+j*n,b+j*n,c);
            add(b+j*n,a+j*n,c);
        }
    }
    for(int i=1;i<=k;++i)
    {
        add(t+(i-1)*n,t+i*n,0);
    }//預防沒有用k次機會就到了

    dijkstra(s);

    cout<<dis[t+n*k]<<endl;

    return 0;
}
void add(int x,int to,int w)
{
    len++;
    edge[len].to=to; edge[len].w=w;
    edge[len].next=head[x];
    head[x]=len;
}
void dijkstra(int s)
{
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;

    priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > points;
    //代碼返回pair的比較結果,先按照pair的first元素升序,first元素相等時,再按照second元素升序:
    points.push(make_pair(0,s));//第一個參數指從s到第二個參數到距離

    while(!points.empty())
    {
        int x=points.top().second;
        points.pop();
        if(!visit[x])
        {
            visit[x]=true;
            for(int i=head[x];i;i=edge[i].next)
            {
                int to=edge[i].to;
                if(dis[to]>dis[x]+edge[i].w)
                {
                    dis[to]=dis[x]+edge[i].w;
                    points.push(make_pair(dis[to],to));
                }
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章