CSP-Floyd算法和傳遞性解題
知識簡述
Floyd算法是圖論算法中求解全源最短路問題的權威算法。雖然其具有較高的O(n^3)複雜度,但是由於全源的特性和動態規劃的求解思路,在解決全局問題(有負邊無負環)時有較好的效果。受制於篇幅,此處不再講述動態規劃的證明問題,後續會退出動態規劃專題進行相關的詳解。
按照上面的步驟,我們可以得出下列的Floyd基本實現代碼:
void Floyd(int n,int dis**)
{
for(int k=1;k<=n;k++)//外面k層是更新的次數
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]);
}
在實現時,要注意到的問題是,調整次數k在最外層,如果你記錯了順序,很可能得到錯誤的距離矩陣。
Floyd算法能夠解決的問題:
1、求取圖上任意兩點間的關係(是否連通)
2、多元負權最短路,求解圖上任意兩點間的距離
3、求解圖上的閉包,求解圖上兩點間的傳遞閉包關係
題目概述
衆所周知,TT 有一隻魔法貓。
這一天,TT 正在專心致志地玩《貓和老鼠》遊戲,然而比賽還沒開始,聰明的魔法貓便告訴了 TT 比賽的最終結果。TT 非常詫異,不僅詫異於他的小貓咪居然會說話,更詫異於這可愛的小不點爲何有如此魔力?
魔法貓告訴 TT,它其實擁有一張遊戲勝負表,上面有 N 個人以及 M 個勝負關係,每個勝負關係爲 A B,表示 A 能勝過 B,且勝負關係具有傳遞性。即 A 勝過 B,B 勝過 C,則 A 也能勝過 C。
TT 不相信他的小貓咪什麼比賽都能預測,因此他想知道有多少對選手的勝負無法預先得知,你能幫幫他嗎?
INPUT&輸入樣例
第一行給出數據組數。
每組數據第一行給出 N 和 M(N , M <= 500)。
接下來 M 行,每行給出 A B,表示 A 可以勝過 B。
輸入樣例:
3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4
OUTPUT&輸出樣例
對於每一組數據,判斷有多少場比賽的勝負不能預先得知。注意 (a, b) 與 (b, a) 等價,即每一個二元組只被計算一次。
輸出樣例:
0
0
4
題目重述
給定直接的勝負關係,在閉包的範圍內進行勝負關係的傳導。
規則:A>B&B>C --> A>C(兩隊之間的勝負關係只存在一種A>B或A<B)
現要求對所有閉包內的進行傳遞求解,並確定出有多少個閉包關係無法確定
思路概述
通讀題目我們可以將題目總結如下:給定閉包的元素和閉包的已知關係,利用勝負關係進行閉包傳遞,最終確定出全部閉包關係。設計閉包和全源問題,自然而然就會想到Floyd算法,但是如果採用:暴力調整n^3次,遍歷求取無法確定的關係個數這種方法的話,在邊數較多時會出現TLE問題,因此對Floyd進行一定的優化至關重要。
根據對傳遞閉包的原理我們可以知道,只有滿足A>B的前提下,滿足第二個條件B>C,才能通過傳遞得出需要調整的關係A>C。如果在遍歷過程中發現A與B關係不已知,可以進行剪枝優化,不再進行內層的遍歷。利用這種方法,我們可以獲取理想的時間性能,從而完成任務。
總結
Floyd算法的實現雖然簡單,但是其體現了動態規劃系列算法路徑調整規劃過程的美感。由於Floyd本身並不複雜且時間複雜度比較高,在比賽中常用到的是對Floyd算法進行一定的修改和優化剪枝,從而設計出能夠滿足要求複雜度的算法。因此,在學習Floyd算法的過程中,不應該只關注於算法本身,而是將其靈活應用,並總結出一些常用的剪枝方法,從而使Floyd能夠取得較好的時間性能。
問題源碼(C++)
#include<iostream>
#include<stdio.h>
using namespace std;
const int M=5e2+1;
int dis[M][M];
int point_num;
int edge_num;
int floyd()
{
int cnt=0;
for(int k=1;k<=point_num;k++)
for(int i=1;i<=point_num;i++)
{
if(dis[i][k]!=1)
{ continue;}
for(int j=1;j<=point_num;j++)
{
if(dis[i][k]==1 && dis[k][j]==1 && dis[i][j]==0)
{
dis[i][j]=1;
cnt++;
}
}
}
return cnt;
}
int main()
{
int group_num=0;
cin>>group_num;
for(int i=1;i<=group_num;i++)
{
int a=0;
int b=0;
int cnt=0;
cin>>point_num>>edge_num;
for(int j=1;j<=point_num;j++)
for(int k=1;k<=point_num;k++)
dis[j][k]=0;
for(int j=0;j<edge_num;j++)
{
cin>>a>>b;
dis[a][b]=1;
cnt++;
}
cnt+=floyd();
int res=(point_num*(point_num-1)/2)-cnt;
cout<<res<<endl;;
}
}