【為什么要優化】
關於SPFA,他死了(懂的都懂)
進入正題。。。
一般來說,我們有三種優化方法。
SLF優化:
SLF優化,即 Small Label First 策略,使用 雙端隊列 進行優化。
一般可以優化15%~20%,在競賽中比較常用。
設從 u 擴展出了 v ,隊列中隊首元素為 k ,若 dis[ v ] < dis[ k ] ,則將 v 插入隊首,否則插入隊尾。
注:隊列為空時直接插入隊尾。
1 deque<int> q; 2
3 inline void spfa(int x) 4 { 5 memset(d,0x3f,sizeof(d)); 6 memset(v,0,sizeof(v)); 7 d[x]=0;v[x]=1; 8 q.push_back(x); 9 while(q.size()) 10 { 11 int index=q.front();q.pop_front(); 12 v[index]=0; 13 for(int i=head[index];i;i=g[i].next){ 14 int y=g[i].ver,z=g[i].edge; 15 if(d[y]>d[index]+z){ 16 d[y]=d[index]+z; 17 if(!v[y]){ 18 if(!q.empty()&&d[y]>=d[q.front()]) q.push_back(y); 19 else q.push_front(y); 20 v[y]=1; 21 } 22 } 23 } 24 } 25 }
LLL優化:
LLL優化,即 Large Label Last 策略,使用 雙端隊列 進行優化。
一般用SLF+LLL可以優化50%左右,但是在競賽中並不常用LLL優化。(所以我就懶得寫了,這是從這個大佬那里嫖來的)
設隊首元素為 k ,每次松弛時進行判斷,隊列中所有 dis 值的平均值為 x 。
若 dist[ k ] > x ,則將 k 插入到隊尾,查找下一元素,直到找到某一個 k 使得 dis[ k ] <= x ,則將 k 出隊進行松弛操作。
1 void spfa(){ 2 int u,v,num=0; 3 long long x=0; 4 list<int> q; 5 for(int i=1;i<=n;i++){path[i]=MAX;vis[i]=false;} 6 path[s]=0; 7 vis[s]=true; 8 q.push_back(s); 9 num++; 10 while(!q.empty()){ 11 u=q.front(); 12 q.pop_front(); 13 num--;x-=path[u]; 14 while(num&&path[u]>x/num){ 15 q.push_back(u); 16 u=q.front(); 17 q.pop_front(); 18 } 19 vis[u]=false; 20 for(int i=head[u];i;i=a[i].next){ 21 v=a[i].to; 22 if(relax(u,v,a[i].w)&&!vis[v]){ 23 vis[v]=true; 24 if(!q.empty()&&path[v]<path[q.front()])q.push_front(v); 25 else q.push_back(v); 26 num++;x+=path[v]; 27 } 28 } 29 } 30 for(int i=1;i<=n;i++)printf("%d ",path[i]); 31 printf("\n"); 32 }
DFS優化:
這種優化顧名思義,就是用dfs的思想代替bfs的思想來優化Bellman-Ford。
常常用於判斷正/負環,時間復雜度可以達到O(m)(m是邊)。思路是,我們每一次dfs的時候如果走回之前dfs過的點,那就是有環,除了這個dfs的標記,我們還可以打另一個vis數組記錄更新過權值的節點,以后就不必重復更新,大大降低復雜度。
不過如果無環的話,那還是上面那兩種優化稍微適用一點。代碼比較短,但是不好擴展。
1 inline bool spfa(int x) 2 { 3 dfs[x]=1; 4 for(int i=head[x];i;i=g[i].next) 5 { 6 int y=g[i].ver,z=g[i].edge; 7 if(!v[y]||d[y]<d[x]+z){ 8 if(dfs[y]) return 0; 9 v[y]=1; 10 d[y]=d[x]+z; 11 if(!spfa(y)) return 0; 12 } 13 } 14 dfs[x]=0; 15 return 1; 16 }