1. 程式人生 > >[轉]SPFA算法的玄學方法

[轉]SPFA算法的玄學方法

thml 部分 需要 有用 定義 菊花 shuf 語句 psu

最近想到了許多優化spfa的方法,這裏想寫個日報與大家探討下

前置知識:spfa(不帶任何優化)

由於使用較多 STLSTL ,本文中所有代碼的評測均開啟 O_2O2? 優化

對一些數組的定義:

dis[i]dis[i] : 起點到 ii 的最短路徑(目前)

inq[i]inq[i] : ii 是否存在隊列當中

現在進入正題


1.一些簡單的優化(?)

SLF(Small Label First)優化 在使用queue作為spfa的輔助數據結構時,將隊列替換為雙端隊列,每當插入元素 nownow時,與隊首進行比較,若 dis[q.front()] > dis[now]dis[q.front()]>dis[now] ,將 nownow 從隊首插入,否則從隊尾插入。

LLL(Large Label Last)優化 同樣使用雙端隊列,維護目前隊列中元素到起點的距離的平均值(即 ∑^{tail}_{i =head}dis[que[i]]/q.size()i=headtail?dis[que[i]]/q.size() ),設該數為 kk ,若 dis[now] > kdis[now]>k ,則從隊尾插入,否則從隊首插入。

由於這兩種優化過於簡單,不給出具體代碼實現。

使用效果:只能讓你的spfa跑的快一點,適用於常數大的同學。至於卡了spfa的題,仍舊沒有什麽用處。

QQ :為什麽這兩種優化有用?

AA :即使圖經過了特殊構造(如網格圖),但邊權大部分隨機的情況下。 SLFSLF 能使得更可能更新出節點最優解的節點最先進行更新,減少無用叠代次數。而 LLLLLL 優化我認為說實話用處不大,因為只要走上了一條權值特別大的邊,這個優化就和沒有一樣了。


2.一些升級過後的優化方式

容錯後的SLF 定義容錯值 valval ,當滿足 dis[now] > dis[q.front()] + valdis[now]>dis[q.front()]+val 時從隊尾插入,否則從隊首插入。

mcfx優化 定義區間 [l,r][l,r] ,當入隊節點的入隊次數屬於這個區間的時候,從隊首插入,否則從隊尾插入。

Swap-SLF 若隊列改變且 dis[q.front()] > dis[q.back()]dis[q.front()]>dis[q.back()] ,交換隊首隊尾

代碼實現以及評測

容錯SLF+MCFX :https://www.luogu.org/record/show?rid=14511935

swap-SLF :https://www.luogu.org/recordnew/show/14512493

(這幾個優化已經能過數據不刁鉆的卡spfa的題,至於lg的模板...fstqwq就是看著這幾個優化來卡的...)

QQ :為什麽這幾種優化有用?

AA :具體我也沒有在網上找到,我說說自己理解的吧。容錯SLF可以讓你的程序不陷入局部最優解,與模擬退火類似;而mcfx優化是這樣的,如過某個節點出發的大多數邊都只能更新一個次解(說白了就是這個點如果是出題人用來故意讓你經過多次的節點,並且每次更新會導致一次特別長的叠代,類似菊花圖的根),那麽它在隊列中的優先級就會降低,就像你知道出題人用這個點來卡你,你竟然還把它最先拿來最先更新,肯定是不夠好的;至於swap-SLF....我也沒有搞懂為什麽這個優化能比普通的SLF快辣麽多(ComeIntoPower說這可以讓隊列更接近優隊)...


3.一些玄學的優化

邊序隨機 將讀入給你的邊隨機打亂後進行spfa

隊列隨機 每個節點入隊時,以1 / 2的概率從隊首入隊,1 / 2的概率從隊尾入隊。

隊列隨機優化版 每 CC 次入隊後,將隊列元素隨機打亂。

使用方法:一般配合上面的優化,當然如果你使用了隊列隨機...那麽你只能靠rp啦。

邊序隨機代碼實現:

//如果你和我一樣是用vector + pair存圖的話,那麽只需要加上這一行語句

for(int i = 1 ; i <= n ; ++i) random_shuffle(G[i].begin() , G[i].end()); 

//以及開頭加上srand(time(NULL));

隊列隨機優化版+邊序隨機代碼實現:

https://www.luogu.org/record/show?rid=14090021


4.更改使用的數據結構

priorty_queue || zkw-segment-tree

當正權邊上的時候,這兩個數據結構優化的spfa與dijkstra相同...

(即使您的代碼允許節點多次入隊,但是也沒什麽用,因為一個節點最多進入一次隊列)

而負權邊的時候,這個算法又被稱為允許多次入隊的dijkstra,但是很危險,有可能被卡成指數級!

stack

dfs實現的spfa,被提出於姜碧野的論文,事實上,它用來判斷負環可能十分快速(沒必要進行多余的 NN 次叠代)。但是如果是求最短路,一般不如隊列版本spfa。為什麽會這樣呢?因為spfa_dfs一個次解就是一個 O(N)O(N) 的叠代,一種圖可以輕而易舉的卡爆它。

技術分享圖片

(ppt是真tm好用)

所以我們采用IDDFS逐漸放寬深度的方式,這樣子spfa_dfs也能趕上spfa_bfs

[轉]SPFA算法的玄學方法