1. 程式人生 > >SPFA算法的SLF優化 ——loj#10081. 「一本通 3.2 練習 7」道路和航線

SPFA算法的SLF優化 ——loj#10081. 「一本通 3.2 練習 7」道路和航線

。。 loj dijkstra 分享 spa 思想 text 超時 我見

今天做到一道最短路的題,原題https://loj.ac/problem/10081

題目大意為給一張有n個頂點的圖,點與點之間有m1條道路,m2條航線,道路是雙向的,且權值非負,而航線是單向的,權值可能為負,保證兩點之間如果有航線就不會有道路。現給定起始點s,求s到每個點的最短路徑,如果沒有則輸出“NO PATH”。

我當時看到這題那叫一個高興啊,以為又是一道水題,因為有負權邊,不能用Dijkstra,果斷用SPFA,那麽有沒有負環呢?經過實測數據並沒有負環,本以為可以輕松AC了,然而評測結果如下:

技術分享圖片

最後兩個測試點T了。手寫隊列,讀入優化,各種常數優化都用完了,還是超時。現在可以基本確定出數據的人賤賤地卡SPFA了。。。

怎麽辦呢,又不能用Dijkstra,這時我找到了某某任學長(聞角大仙)。

在某某任學長的幫助下,我了解了一下SPFA的SLF優化。簡單地說就是用雙端隊列來實現SPFA,當要把一個節點入隊時,判斷它與隊頭的大小,如果dis[v]<dis[h](v為帶入隊節點,t為隊頭),就把它從隊頭插入,否則從隊尾插入。有點貪心的思想,如果這個點比隊頭還要小的話,就先用它來松弛其它節點。部分代碼如下:

1 inline void SPFA(int s)
 2 {
 3     register int p,h,v,w;
 4     fill(dis+1,dis+n+1,INF);
 5     dis[s]=0
; 6 vis[s]=true; 7 q.push_back(s); 8 do 9 { 10 h=q.front();q.pop_front(); 11 vis[h]=false; 12 for(p=tail[h];p;p=e[p].last) 13 { 14 v=e[p].v;w=e[p].w; 15 if(dis[v]>dis[h]+w) 16 { 17 dis[v]=dis[h]+w;
18 if(!vis[v]) 19 { 20 if(dis[v]<dis[q.front()])//這個判斷很重要,如果v點比隊頭更優,就把它從隊頭入隊 21 q.push_front(v); 22 else q.push_back(v); 23 vis[v]=true; 24 } 25 } 26 } 27 }while(!q.empty()); 28 }

提交後測試情況:

技術分享圖片

快了好幾倍啊!

這裏我用的手寫的雙端隊列,因為比用STL更快,附上手寫代碼,這應該是我見過最優秀的手寫雙端隊列方式了:

struct Deque{
    LL l, r, q[N];
    Deque() {l = 0; r = 0;}
    bool empty() {return !(l ^ r);}
    void push_back(LL v) {q[r++] = v; r %= N;}
    void push_front(LL v) {q[l = (l - 1 + N) % N] = v;}
    void pop_front() {++l; l %= N;}
    void pop_back() {r = (r - 1 + N) % N;}
    LL front() {return q[l];}
};

此外SPFA還有LLL優化,這裏就不贅述了(其實是筆者不會),有興趣的朋友可以去了解一下。

2018-08-17

SPFA算法的SLF優化 ——loj#10081. 「一本通 3.2 練習 7」道路和航線