AcWing 383 觀光

題目描述:

“您的個人假期”旅行社組織了一次比荷盧經濟聯盟的巴士之旅。

比荷盧經濟聯盟有很多公交線路。

每天公共汽車都會從一座城市開往另一座城市。

沿途汽車可能會在一些城市(零或更多)停靠。

旅行社計劃旅途從 S 城市出發,到 F 城市結束。

由於不同旅客的景點偏好不同,所以爲了迎合更多旅客,旅行社將爲客戶提供多種不同線路。

遊客可以選擇的行進路線有所限制,要麼滿足所選路線總路程爲 S 到 F 的最小路程,要麼滿足所選路線總路程僅比最小路程多一個單位長度。

3463_1.png

如上圖所示,如果S = 1,F = 5,則這裏有兩條最短路線1->2->5,1->3->5,長度爲6;有一條比最短路程多一個單位長度的路線1->3->4->5,長度爲7。

現在給定比荷盧經濟聯盟的公交路線圖以及兩個城市 S 和 F,請你求出旅行社最多可以爲旅客提供多少種不同的滿足限制條件的線路。

輸入格式

第一行包含整數 T,表示共有 T 組測試數據。

每組數據第一行包含兩個整數 N 和 M,分別表示總城市數量和道路數量。

接下來 M 行,每行包含三個整數 A,B,L,表示有一條線路從城市 A 通往城市 B,長度爲 L。

需注意,線路是 單向的,存在從A到B的線路不代表一定存在從B到A的線路,另外從城市A到城市B可能存在多個不同的線路。

接下來一行,包含兩個整數 S 和 F,數據保證 S 和 F 不同,並且S、F之間至少存在一條線路。

輸出格式

每組數據輸出一個結果,每個結果佔一行。

數據保證結果不超過10^9。

數據範圍

2≤N≤1000,
1≤M≤10000,
1≤L≤1000,
1≤A,B,S,F≤N

輸入樣例:

2
5 8
1 2 3
1 3 2
1 4 5
2 3 1
2 5 3
3 4 2
3 5 4
4 5 3
1 5
5 6
2 3 1
3 2 1
3 1 10
4 5 2
5 2 7
5 2 7
4 1

輸出樣例:

3
2

分析:

本題雖然屬於dijkstra算法的擴展應用,但是從不論是解題思路還是最終AC都沒那麼容易。題目的關鍵字有:正權邊,存在重邊,求最短路徑條數與比最短路徑長度多一的路徑條數之和,換而言之,就是求出最短路徑條數與次短路徑條數之和,規定次短路徑只能比最短路徑長度多一。

先來說下我最初的思路,雖然只過了一半用例,但是其思想還是值得深究的。dijkstra在求最短路徑的過程中核心思想是鬆弛操作,即d[j] = min(d[j],d[u] + w),這個式子充滿了DP的思想,只不過不是按照正常的線性順序去擴展狀態的。現在對於一個節點而言,不僅有這個起點到節點的最短距離以及最短路徑的條數這兩個狀態,還有次短距離以及次短路徑的條數這兩個狀態,當狀態比較多時,用狀態機去求解就很方便了。f[i]和c1[i]表示到i點的最短路徑長度和條數,g[i]和c2[i]表示到i點次短路徑的長度和條數。當c2[i]不爲0時,g[i] == f[i] + 1,c2[i]爲0時表示沒有次短路徑。下面分析狀態的轉移,點j的最短路只能由點u的最短路轉移而來,即d[j] = min(d[j],d[u] + w)這個式子還是不變的,c1[j]在f[j]被更新時同步被更新爲c1[u],在遇見等於最短路徑的情況時c1[j] += c1[u]。次短路徑可能由u的次短路徑轉移而來,也可能由u的最短路徑轉移而來。如果u的最短路徑加上邊權恰好是j的最短路徑,那麼u的次短路徑加上邊權就是j的次短路徑;如果u的最短路徑加上邊權恰好比j的最短路徑大一,則u的最短路徑加上邊權就是j的次短路徑,用式子表示就是f[u] + w < f[j]時,f[j] = f[u] + w,g[j] = g[u],c1[j] = c1[u],c2[j] = c2[u];f[u] + w == f[j]時,c1[j] += c1[u],c2[j] += c2[u];f[u] + w == f[j] + 1時,g[j] = g[u],c2[j] += c2[u];如果畫個狀態圖上面狀態轉移的關係就一目瞭然了。我最初的思路就是dijkstra算法主體不變,只將最短路徑長度加入堆中,然後在堆頂元素出堆時,加上上面的狀態轉移操作。邏輯似乎沒有錯誤,但是爲什麼只過了一半用例呢?這是因爲動態規劃要求狀態的無後效性,dijkstra算法正確性的前提就是取出堆中距離最小的頂點後,它的最短距離後面不會被更新爲更小的了,這就可以理解爲狀態的無後效性,一個頂點出隊時它的狀態就不會再改變了。

如上圖所示,dijkstra求最短路長度和條數的時候A最先出堆,然後是B和C出堆,然後C出堆,然後D出堆,我們假設C比B先出堆,C出堆後,A經過B是不可能更新C的最短路的,所以C的最短路徑長度和最短路徑的條數都不會被更新了。但是如果同時更新次短路的狀態呢?A出堆後更新C和B的狀態,然後C出堆,此時C的最短路徑長度是1,最短路徑路徑條數是1,然後C去更新D的狀態,D的次短路徑此時是不存在的,次短路徑的條數自然是0;然後B出堆,更新了C的次短路狀態,但是C已經出堆了,不會再去更新D的次短路狀態了,這時的算法就出錯了。用一句話概括下就是我們固然可以通過一個頂點的狀態去更新另一個頂點的狀態,但是必須保證前一個頂點(出堆的時候)狀態已經確定下來了,否則B更新C的狀態後無法傳遞到D。

正確求解本題只需要將上述思想略加調整,既然dijkstra算法出堆時只有頂點的最短路信息被確定了,那麼我們將次短路信息也加入到堆中,只有一個頂點次短路出堆時,它的次短路信息纔會被確定下來。對應在算法上的改變就是B在更新C的狀態時,不僅需要嘗試把C的最短路徑長度加入堆中(當然這裏1 + 1 > 1沒有鬆弛成功不用入堆),C的次短路徑長度被更新爲2也要加入到堆中,然後儘管C作爲最短路時出堆了,作爲次短路還可以再出堆一次,出堆時C的次短路信息就定下來了,也可以去更新D的次短路信息了。

整理下思路,我們需要放進優先級隊列中頂點的信息有:路徑長度、節點編號、路徑類型,類型type爲0時表示最短路徑,type爲1時表示次短路徑,在執行狀態轉移時這裏並不需要分類討論出堆的是最短路徑還是次短路徑。設dist爲路徑長度加上邊權之和,當dist小於最短路徑長度時,嘗試更新最短路徑和次短路徑,這時不用管type的原因在於只有最短路徑纔會滿足這個分支條件;當dist等於最短路徑長度時,更新下最短路徑信息即可,這個分支被觸發的前提也只可能是type是最短路徑的情況下,如果u的次短路徑出隊加上邊權等於j的最短路徑的話,先於u的次短路徑出堆的最短路徑出堆時一定會更新掉j的最短路徑;當dist等於最短路徑長度+1時,如果j的次短路徑條數爲0表明這是第一次更新j的次短路,需要將次短路入隊,同時更新次短路信息,這個分支type去0或者1的情況下都可能觸發,對應了上面分析中次短路徑可以由次短路徑或者最短路徑轉移而來。具體的實現見代碼:

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1005,M = 10005;
int idx,h[N],e[M],w[M],ne[M];
int n,m,d[N][2],cnt[N][2];
bool st[N][2];
struct Node{
    int dist,idx,type;
    bool operator < (const Node X)const{
        return dist > X.dist;//把小於號定義爲大於號,大根堆就變成小根堆了
    }
};
priority_queue<Node> pq;
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra(int s,int t){
    memset(d,0x3f,sizeof d);
    memset(cnt,0,sizeof cnt);
    memset(st,false,sizeof st);
    d[s][0] = 0,cnt[s][0] = 1;
    pq.push({d[s][0],s,0});
    while(pq.size()){
        Node node = pq.top();
        int u = node.idx,type = node.type;
        pq.pop();
        if(st[u][type])   continue;
        st[u][type] = true;
        for(int i = h[u];~i;i = ne[i]){
            int j = e[i];
            int dist = d[u][type] + w[i];
            if(dist < d[j][0]){
                if(dist + 1 == d[j][0]){
                    d[j][1] = d[j][0],cnt[j][1] = cnt[j][0]; 
                    pq.push({d[j][1],j,1});
                }
                d[j][0] = dist,cnt[j][0] = cnt[u][0];
                pq.push({d[j][0],j,0});
            }
            else if(dist == d[j][0])    cnt[j][0] += cnt[u][0];
            else if(dist == d[j][0] + 1){
                if(!cnt[j][1]){
                    d[j][1] = dist;
                    pq.push({d[j][1],j,1});
                }
                cnt[j][1] += cnt[u][type];//這裏一定要寫成type,因爲type是0或者1都可能觸發該分支
            }   
        }
    }
    return cnt[t][0] + cnt[t][1];
}
int main(){
    int T,S,F,a,b,c;
    scanf("%d",&T);
    while(T--){
        memset(h,-1,sizeof h);
        idx = 0;
        scanf("%d%d",&n,&m);
        for(int i = 0;i < m;i++){
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
        }
        scanf("%d%d",&S,&F);
        printf("%d\n",dijkstra(S,F));
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章