Floyd算法及其应用
Part I-Introduction
Floyd算法是一種求圖上多源最短路徑的算法,適用于中小規模的圖,思維簡單易懂。
Floyd算法的實質是(區間)動態規劃,在這里做一個簡單的概述。
對于一個有\(n\)個結點的圖,
令\(dis[i][j]\)為結點\(i\)到結點\(j\)的最短路徑長度。
首先,將所有現成的邊都存入\(dis\),其余的令其值\(=\infty\),并使\(dis[i][i]=0\)。
接著,枚舉中轉點(\(k\)),那么:
\[dis[i][j]=\min\{dis[i][k]+dis[k][j]\text{ | }k\in[1,n],k\ne i,k\ne j\}\]
代碼實現:
void Floyd() {memset(dis,0x3f3f3f3f,sizeof(dis));for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(a[i][j]!=0x3f3f3f3f)邊(i,j)存在。dis[i][j]=a[i][j];//a為現存的邊。for(int i=1;i<=n;i++) dis[i][i]=0;for(int k=1;k<=n;k++)//枚舉中轉點。for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){if(i==j||j==k||k==i) continue;dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);} }Attention:循環時\(k\)必須放在第一層。若將\(i\)置于第一層,就會導致i->k->j的距離過早的確定下來,可能會導致答案錯誤。
注:其實最原始的Floyd中\(dis\)的定義是:\(dis[i][j][k]\)表示結點\(i\)到結點\(j\)只經過結點\(i-k\)的最短路徑。 則有:\(dis[i][j][k]=min(dis[i][j][k-1],dis[i][k][k-1]+dis[k][j][k-1])\),降維得到現在的方程。
時間復雜度:\(O(n^3)\)。
Part II-Sevral Simple Problems
Floyd算法可以解決許多看似無法處理的問題。
Problem[1]:[USACO09JAN] Best Spot
鏈接:https://www.luogu.org/problemnew/show/P2935
題面:
此題較為簡單,算法流程:
用Floyd處理出各個點間的最短路徑。
計算出每個點到各個Favorites的總距離。
選出總距離最小的點輸出即可。
Problem[2]:[JSOI2007]重要的城市
鏈接:https://www.luogu.org/problemnew/show/P1841
題面:
這一題比上一題略難,如何記錄中轉點?
Of course,在Floyd的時候。
令\(c[i][j]\)為結點\(i,j\)之間的最短路的中轉點,若無則為0;
在進行對\(dis[i][j]\)的更新之時,我們不直接取min,而是先判斷以避免覆蓋。
因為我們還要進行一個更重要的操作,that is,更新\(c[i][j]\)!
分情況討論:
1.\(dis[i][j]>dis[i][k]+dis[k][j]\)(需要更新):此時結點\(i,j\)之間的最短路的中轉點就要發生改變,即\(c[i][j]=k\),并更新\(dis[i][j]\)的值。
2.\(dis[i][j]=dis[i][k]+dis[k][j]\) :這不僅說明原先的\(c[i][j]\)已經失效,而且意味著此時已經不存在\(c[i][j]\)了(并不需要中轉點就有最短路了)。因此令\(c[i][j]=0\)。
3.\(dis[i][j]<dis[i][k]+dis[k][j]\):此時的中轉點無法得到更優的解,忽略。
這樣我們就處理好了\(c\)。對于結果的處理,可以利用桶排序的思想,令\(res[c[i][j]]=1\)。(res[N]:bool)
最后遍歷\(res\),輸出答案(別忘了無解的處理!!!)。
#include<cstdio> #include<bitset> #include<cstring> using namespace std;const int N=205; int c[N][N]; int f[N][N]; int n,m;void Solve() {for(int i=1;i<=n;i++) f[i][i]=0;for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){if(i==j||j==k||i==k) continue;if(f[i][j]>f[i][k]+f[k][j])f[i][j]=f[i][k]+f[k][j],c[i][j]=k;else if(f[i][j]==f[i][k]+f[k][j])c[i][j]=0;} }bool res[N],flag=0; signed main() {memset(f,0x3f3f3f3f,sizeof(f));memset(c,0,sizeof(c));scanf("%d%d",&n,&m);for(int u,v,w,i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);f[u][v]=f[v][u]=w;//f:=dis}Solve();for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)res[c[i][j]]=1;for(int i=1;i<=n;i++)if(res[i]) printf("%d ",i),flag=1;if(!flag) puts("No important cities.");return 0; }Problem[3]:[NOI2007]社交網絡
鏈接:https://www.luogu.org/problemnew/show/P2047
題面:
這題貌似比上一題更復雜——要進行路徑計數。
令\(cnt[i][j]\)為結點\(i,j\)之間的最短路徑條數。
還是在處理\(dis\)的時候處理\(cnt\)。
在分類討論之前,你需要知道一件事情:
假設我們已經處理完了\(cnt[i][k]\)和\(cnt[k][j]\),那么怎么知道\(cnt[i][j]\)的值? 對于\(cnt[i][k]\)中的每條路徑,與\(cnt[k][j]\)配對,有\(cnt[k][j]\)條路徑。 那么\(cnt[i][k]\)條一起配對就是\(cnt[i][k]\times cnt[k][j]\)條,這就是\(cnt[i][j]\)的值。(說白了就是乘法原理)
那么,再開始分類討論:
1.\(dis[i][j]=dis[i][k]+dis[k][j]\):又找到了一坨解,\(cnt[i][j]+=cnt[i][k]*cnt[k][j]\)(注意是+=!)。
2.\(dis[i][j]>dis[i][k]+dis[k][j]\) :這代表有了新的解,原先的答案不能再用了,\(cnt[i][j]=cnt[i][k]*cnt[k][j]\)(注意是=!)。
3.\(dis[i][j]<dis[i][k]+dis[k][j]\):此時的中轉點無法得到更優的解,忽略。
處理完\(cnt\)后,那就意味著\(C_{s,t},C_{s,t}(v)\)什么的都出來了。
P.S.:建議cnt用double。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std;const int N=105; int dis[N][N]; double cnt[N][N]; int n,m;signed main() {memset(dis,0x3f3f3f3f,sizeof(dis));memset(cnt,0,sizeof(cnt));scanf("%d%d",&n,&m);for(int u,v,w,i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);dis[u][v]=dis[v][u]=min(w,dis[u][v]);cnt[u][v]=cnt[v][u]=1;}for(int i=1;i<=n;i++)dis[i][i]=0;for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){if(i==j||i==k||j==k) continue;if(dis[i][j]==dis[i][k]+dis[k][j])cnt[i][j]+=cnt[i][k]*cnt[k][j];else if(dis[i][j]>dis[i][k]+dis[k][j]){dis[i][j]=dis[i][k]+dis[k][j];cnt[i][j]=cnt[i][k]*cnt[k][j];}}for(int k=1;k<=n;k++){double ans=0;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){if(i==j||i==k||j==k) continue;if(dis[i][j]==dis[i][k]+dis[k][j])ans+=cnt[i][k]*cnt[k][j]*1.0/cnt[i][j];}printf("%.3lf\n",ans);} }Part III-EXT:最小環問題
來看這么一個問題:http://acm.hdu.edu.cn/showproblem.php?pid=1599
題面:
看似無從下手,但仍然是Floyd!
在更新\(dis\)前,我們已經把\(1-(k-1)\)的情況處理好了。那么當前的最小環就是:
\[\min\{a[i][k]+a[k][j]+dis[i][j]\}\]
先貼代碼:
#include<cstdio> #include<algorithm> #include<cstring> using namespace std;const int N=105; int a[N][N]; int dis[N][N]; int n,m;long long solve() {long long min_circle=0x3f3f3f3f;for(int k=1;k<=n;k++){for(int i=1;i<k;i++)for(int j=i+1;j<k;j++)min_circle=min(min_circle,1ll*a[i][k]+a[k][j]+dis[i][j]);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);}return min_circle; }signed main() {while(scanf("%d%d",&n,&m)!=EOF){memset(a,0x3f3f3f3f,sizeof(a));for(int u,v,w,i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);a[u][v]=a[v][u]=min(a[u][v],w);}for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dis[i][j]=a[i][j];long long res=solve();if(res!=0x3f3f3f3f) printf("%lld\n",res);else puts("It's impossible.");}return 0; }值得注意的是,\(i,j\)的循環范圍的控制,因為\(i,j,k\)不能相同。
至于為什么先處理最小環在更新路徑,當然是為了使\(dis[i][j]\)不經過\(k\)啊。
轉載于:https://www.cnblogs.com/-Wallace-/p/11165803.html
總結
以上是生活随笔為你收集整理的Floyd算法及其应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Microsoft Visual Stu
- 下一篇: 套接字TCP控制台程序客户端代码示范