1. 程式人生 > >動態刪邊SPFA: [HNOI2014]道路堵塞

動態刪邊SPFA: [HNOI2014]道路堵塞

 [HNOI2014]道路堵塞

題目描述

A國有N座城市,依次標為1到N。同時,在這N座城市間有M條單向道路,每條道路的長度是一個正整數。現在,A國交通部指定了一條從城市1到城市N的路徑,並且保證這條路徑的長度是所有從城市1到城市N的路徑中最短的。不幸的是,因為從城市1到城市N旅行的人越來越多,這條由交通部指定的路徑經常發生堵塞。現在A國想知道,這條路徑中的任意一條道路無法通行時,由城市1到N的最短路徑長度是多少。

輸入輸出格式

輸入格式:

輸入檔案第一行是三個用空格分開的正整數N、M和L,分別表示城市數目、單向道路數目和交通部指定的最短路徑包含多少條道路。按下來M行,每行三個用空格分開的整數a、b和c,表示存在一條由城市a到城市b的長度為c的單向道路。這M行的行號也是對應道路的編號,即其中第1行對應的道路編號為1,第2行對應的道路編號為2,...,第M行對應的道路編號為M。最後一行為L個用空格分開的整數sp(1)...,,sp(L),依次表示從城市1到城市N的由交通部指定的最短路徑上的道路的編號。

輸出格式:

輸出檔案包含L行,每行為一個整數,第i行(i=1,2...,,L)的整數表示刪去編號為sp(i)的道路後從城市1到城市N的最短路徑長度。如果去掉後沒有從城市1到城市N的路徑,則輸出一1。

輸入輸出樣例

輸入樣例
4 5 2
1 2 2
1 3 2
3 4 4
3 2 1
2 4 3
1 5
輸出樣例:
6
6

 

主要是網上關於這題的題解分析太少了 希望能寫一篇可以讓大家看懂的……

這題是一道動態刪邊SPFA + 堆的題 說白了,就是SPFA亂搞

理論基礎

首先,本題很友好的把一開始的最短路告訴了我們,且刪掉的邊都在最短路上,(正常動態刪邊,如果不是刪的最短路上的邊,直接輸出最短路即可,所以本題就是動態刪邊最難搞的一部分,刪了最短路上的邊)

當你刪掉一條邊(u -> u+1)時,最短路必為 1 -> x(x <= u) -> y(y >= u+1) -> n 其中有且僅有 1 -> x 與 y -> n 為原最短路上的路線

比如:

該圖的最短路很明顯為1->3->4->5

如果我們任意去掉一條最短路上的邊(1->3或3->4或4->5),很不巧,答案都是1->2->5(Lenmin = 7),均滿足1 -> x(x <= u) -> y(y >= u+1) -> n(好吧本圖有點特殊)

搞定這個後,我們可以先把所有最短路上的點記錄一下,並將其到1及到n的距離預處理出來

    st[1] = pos[1] = 1;
    for(register int i = 1;i<=L;i++)
    {
        scanf("%d",&path[i]);
        pos[ed[path[i]]] = i+1;//該點在最短路上是第幾個
        st[i+1] = ed[path[i]];//最短路上第i+1個點是哪個點
    }
    for(register int i = 2;i<=L;i++)f[i] = f[i-1] + vlu[path[i-1]];//Why not + -> L+1(n點):只刪L條邊 不可能從n點為起點 
    for(register int i = L;i>=2;i--) g[i] = g[i+1] + vlu[path[i]]; // Why not 是 i -> 2 同 ↑

接著列舉每一條最短路上的邊,刪了它並分別做一次SPFA

重點來了,怎麼SPFA!

首先是普通的vis和dis及queue

設起點為u

當搜到一個普通點時,和原SPFA一樣,但如果搜到一個在最短路上的點且這個點在最短路上排在u的後面,就滿足了上面的條件,有可能是最優解,我們可以把它放入一個棧s裡,且打個標記該點在棧裡;如果已經在棧裡,只要更新它的x(tmp陣列中的x即為value,id為該點的編號)

最後搜完後將所有可能為答案的點及它所帶來的答案加入到一個小根堆,然後彈出編號在u之前的點,則小根堆的top的答案即為最短路答案

可能會有的一些問題(有一些寫為程式碼註釋)

1.Why dis不用memset

如上圖,第一條邊做好後,dis[2]是3,如果memset後,從3開始搜,dis[2]就會變為5,就再也找不到1-2-5了;所以dis在本題的實質是以1-x(正在搜的點)為起點的最短的距離

2.在SPFA中,加入棧的都被特判過是在u之後,為什麼還要彈出點

咳咳,同學,我們在搜下一條邊之前並沒有清空操作,有可能有是之前的答案但不符合現在的答案

3.……本人暫時沒想到,不過有問題隨便問

對了,上程式碼!

#include<bits/stdc++.h>
#define MAXN 110000
#define MAXM 210000
#define INF 2147483647
using namespace std;
int pos[MAXN],st[MAXN]; // pos[i] i點為原最短路上的第幾個點 st[i]  最短路上的第i個點是誰 
int hed[MAXN],nxt[MAXM],ed[MAXM],vlu[MAXM],l = 0; // 普通鄰接表
int dis[MAXN];
int path[MAXN]; // 原最短路 
bool used[MAXN];//該點是否在棧裡
int f[MAXN],g[MAXN]; // f[i]表示 1 - st[i] g[i] 表示 ed[i] - n 
int n,m,L;
struct Point 
{
    int x,id;
}tmp[MAXN];
inline bool operator < (const Point & A,const Point & B) {return A.x > B.x;}//變為小根堆
priority_queue < Point > Q;
inline void Line(int u,int v,int w) // 鄰接表加邊 
{
    nxt[++l] = hed[u];
    hed[u] = l;
    ed[l] = v;
    vlu[l] = w;
}
inline int read() 
{
    int s = 0;char ch = getchar();
    while(ch < '0' || ch > '9') ch = getchar();
    while(ch >= '0' && ch <= '9') {s = s * 10 + ch - '0';ch = getchar();}
    return s;
}
inline void SPFA(int u)
{
    bool vis[MAXN];
    memset(vis,0,sizeof(vis));
    memset(used,0,sizeof(used));
    stack <int> s;
    queue <int> q;
    q.push(st[u]);
    vis[st[u]] = 1;
    dis[st[u]] = f[u];
    while(!q.empty())
    {
        int k = q.front();
        q.pop();
        vis[k] = 0;
        for(register int i = hed[k];i;i = nxt[i])
        {
            if(i == path[u]) continue;
            if(pos[ed[i]] > u)//可能是答案
            {
                if(!used[ed[i]])//將該點加入棧裡
                {
                    used[ed[i]] = 1;
                    s.push(ed[i]);
                    tmp[ed[i]].id = pos[ed[i]];
                    tmp[ed[i]].x = dis[k] + vlu[i] + g[pos[ed[i]]];
                }
                else tmp[ed[i]].x = min(tmp[ed[i]].x,dis[k] + vlu[i] + g[pos[ed[i]]]);//將該點的value更新
            }
            else if(dis[ed[i]] > dis[k] + vlu[i])//普通點
            {
                dis[ed[i]] = dis[k] + vlu[i];
                if(!vis[ed[i]]) q.push(ed[i]),vis[ed[i]] = 1;
            }
        }
    }
    while(!s.empty()) Q.push(tmp[s.top()]),s.pop();//將點出棧
}
int main()
{
    n = read(),m = read(),L = read();
    for(register int i = 1;i<=m;i++)
    {
        int a = read(),b = read(),c = read();
        Line(a,b,c);
    }
    st[1] = pos[1] = 1;
    for(register int i = 1;i<=L;i++)
    {
        scanf("%d",&path[i]);
        pos[ed[path[i]]] = i+1;
        st[i+1] = ed[path[i]];
    }
    for(register int i = 2;i<=L;i++) f[i] = f[i-1] + vlu[path[i-1]];//Why not + -> L+1(n點):只刪L條邊 不可能從n點為起點 
    for(register int i = L;i>=2;i--) g[i] = g[i+1] + vlu[path[i]]; // Why not i -> 2 同 ↑
    for(register int i = 1;i<=n;i++) dis[i] = INF;
    for(register int i = 1;i<=L;i++)
    {
        SPFA(i);
        while(!Q.empty() && Q.top().id <= i) used[Q.top().id] = 0,Q.pop();//將無用的點去掉
        if(Q.empty()) printf("-1\n");
        else printf("%d\n",Q.top().x);
    }
    return 0;
}

如果看完後有什麼問題 一定要問呀!