luogu P3371 & P4779 ---單源最短路徑spfa & 最大堆優化Dijkstra
P3371 【模板】單源最短路徑(弱化版)
題目背景
本題測試數據為隨機數據,在考試中可能會出現構造數據讓SPFA不通過,如有需要請移步 P4779。
題目描述
如題,給出一個有向圖,請輸出從某一點出發到所有點的最短路徑長度。
輸入輸出格式
輸入格式:
第一行包含三個整數N、M、S,分別表示點的個數、有向邊的個數、出發點的編號。
接下來M行每行包含三個整數Fi、Gi、Wi,分別表示第i條有向邊的出發點、目標點和長度。
輸出格式:
一行,包含N個用空格分隔的整數,其中第i個整數表示從點S出發到點i的最短路徑長度(若S=i則最短路徑長度為0,若從點S無法到達點i,則最短路徑長度為2147483647)
輸入輸出樣例
輸入樣例#1:4 6 1 1 2 2 2 3 2 2 4 1 1 3 5 3 4 3 1 4 4輸出樣例#1:
0 2 4 3
說明
時空限制:1000ms,128M
數據規模:
對於20%的數據:N<=5,M<=15;
對於40%的數據:N<=100,M<=10000;
對於70%的數據:N<=1000,M<=100000;
對於100%的數據:N<=10000,M<=500000。保證數據隨機。
對於真正 100% 的數據,請移步 P4779。請註意,該題與本題數據範圍略有不同。
樣例說明:
圖片1到3和1到4的文字位置調換
解題思路:spfa算法是Bellman-Ford算法的隊列優化算法的別稱,通常用於求含負權邊的單源最短路徑,以及判負權環。spfa最壞情況下復雜度和樸素Bellman-Ford一樣為O(|V||E|)。在非負邊權圖中為了避免出現最壞情況,通常使用效率更加穩定的Dijkstra算法,以及它的堆優化版本。數據較水,直接拿spfa板子水過。spfa思想:每次只需對上一次更新過的點的鄰接點進行更新即可,因為上一次更新的點只對其未更新的鄰接點有影響,所以無需像bellman-ford算法一樣盲目遍歷每條邊。另外,出隊的頂點要標記為false,即出隊的那些點仍有可能對前面已更新過的點到源點的距離再進行更新,同時也要保證每個頂點至多在隊列中出現1次,避免浪費時間去重復更新。
AC代碼:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 const int maxn=1e5+5; 5 const int inf=0x3f3f3f3f; 6 queue<int> que;vector<int> v1[maxn],v2[maxn];int n,m,s,u,v,w,x,y,z,dis[maxn];bool vis[maxn]; 7 void spfa(int s){ 8 while(!que.empty())que.pop();9 que.push(s),vis[s]=true,dis[s]=0; 10 while(!que.empty()){ 11 x=que.front();que.pop(),vis[x]=false;//出隊則標記不在隊列中 12 for(size_t j=0;j<v1[x].size();++j){ 13 y=v1[x][j],z=v2[x][j]; 14 if(dis[x]+z<dis[y]){ 15 dis[y]=dis[x]+z; 16 if(!vis[y])que.push(y),vis[y]=true;//標記在隊列中 17 } 18 } 19 } 20 } 21 int main(){ 22 while(cin>>n>>m>>s){ 23 for(int i=0;i<=n;++i)v1[i].clear(),v2[i].clear(); 24 memset(vis,false,sizeof(vis)); 25 while(m--){ 26 cin>>u>>v>>w; 27 v1[u].push_back(v);//鄰接點 28 v2[u].push_back(w);//對應邊權 29 } 30 memset(dis,0x3f,sizeof(dis)); 31 spfa(s); 32 for(int i=1;i<=n;++i)cout<<(dis[i]==inf?2147483647:dis[i])<<(i==n?‘\n‘:‘ ‘); 33 } 34 return 0; 35 }
P4779 【模板】單源最短路徑(標準版)
題目背景
2018 年 7 月 19 日,某位同學在 NOI Day 1 T1 歸程 一題裏非常熟練地使用了一個廣為人知的算法求最短路。
然後呢?
100→60;
Ag→Cu;
最終,他因此沒能與理想的大學達成契約。
小 F 衷心祝願大家不再重蹈覆轍。
題目描述
給定一個 NN 個點,MM 條有向邊的帶非負權圖,請你計算從 SS 出發,到每個點的距離。
數據保證你能從 SS 出發到任意點。
輸入輸出格式
輸入格式:
第一行為三個正整數 N, M, SN,M,S。 第二行起 MM 行,每行三個非負整數 u_i, v_i, w_iui?,vi?,wi?,表示從 u_iui? 到 v_ivi? 有一條權值為 w_iwi? 的邊。
輸出格式:
輸出一行 NN 個空格分隔的非負整數,表示 SS 到每個點的距離。
輸入輸出樣例
輸入樣例#1:4 6 1 1 2 2 2 3 2 2 4 1 1 3 5 3 4 3 1 4 4輸出樣例#1:
0 2 4 3
說明
樣例解釋請參考 數據隨機的模板題。
1≤N≤100000;
1≤M≤200000;
S=1;
1≤ui?,vi?≤N;
0≤wi?≤10^9,
0≤∑wi?≤10^9。
本題數據可能會持續更新,但不會重測,望周知。
2018.09.04 數據更新 from @zzq
解題思路:堆優化Dijkstra算法,其實就是降低了每次去查找當前dis數組中最小值的時間,時間復雜度由原來的O(|V|2)降為O(|E|log|V|)。
AC代碼:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=1e5+5; 4 int n,m,s,u,v,w,x,y,z,len,dis[maxn];vector<int> v1[maxn],v2[maxn]; 5 priority_queue< pair<int,int> > que;//最大堆優先隊列 6 void Dijkstra(){ 7 while(!que.empty())que.pop(); 8 dis[s]=0;que.push(make_pair(-dis[s],s));//加上負號實現最大堆,便於取出最短路徑 9 while(!que.empty()){ 10 x=que.top().second,len=-que.top().first;que.pop(); 11 if(dis[x]<len)continue;//過濾掉已經更新過的頂點,表示當前頂點x到源點的距離dis[x] 12 for(size_t j=0;j<v1[x].size();++j){ 13 y=v1[x][j],z=v2[x][j]; 14 if(dis[x]+z<dis[y])dis[y]=dis[x]+z,que.push(make_pair(-dis[y],y)); 15 } 16 } 17 } 18 int main(){ 19 while(~scanf("%d%d%d",&n,&m,&s)){ 20 for(int i=0;i<=n;++i)v1[i].clear(),v2[i].clear(); 21 for(int i=0;i<=n;++i)dis[i]=2e9; 22 while(m--){ 23 scanf("%d%d%d",&u,&v,&w); 24 v1[u].push_back(v); 25 v2[u].push_back(w); 26 } 27 Dijkstra(); 28 for(int i=1;i<=n;++i)printf("%d%c",dis[i],i==n?‘\n‘:‘ ‘); 29 } 30 return 0; 31 }
luogu P3371 & P4779 ---單源最短路徑spfa & 最大堆優化Dijkstra