題意: N個城市M條邊,邊是無向變。每個邊有權值,以及每個城市都有某個數量的人。
給出起點城市和終點城市,要求從起點到終點的最短距離條件下的—— 路徑數,以及最多能捎上多少人!(路徑上城市的人數和最大)
難點:
數據量N小於500。作爲PAT第三題,是最短路的路徑數問題以及小變形。Dijkstra、Bellman什麼的基本操作都學過,但是一是忘了大半,二是沒有吃透。所以今天做這個簡單的小變形,1個半小時還沒有做出來。較難以理解如何 “在鬆弛點上如何變形” 。
參照了別人的AC代碼,果不其然是在鬆弛點上操作。要分別將 “路徑數”的維護操作 以及 “路徑經過的最大人數” 維護操作,在鬆弛操作時候一同聯繫進去,說實話,雖然知道代碼怎麼寫,但還是沒有理解爲什麼可以這樣。。。。
這種小變形應該是算很簡單的小變形, 以至於各位江湖朋友寫博客都不寫爲什麼這麼寫,往往只是寫 “Dijkstra的小變形” 。。。。emmmm ,睡了一覺,第二天清醒 的腦袋,一下就理解了爲什麼可以這樣寫。。。雖然我還是解釋不清啊。。。
1、本題我使用的是Dijkstra算法
2、每一輪找到最近未標記過點u之後,按照Dijkstra算法,接下來是用點u去嘗試鬆弛剩下的所有點。在這裏:
如果通過點u,無法鬆弛某點 i ,但是路徑大小相同,那麼這可能就是一條新的最短路徑,那麼到達 i 點的方法數 = 自己原本的值 + 到u點方法數 。
如果通過點u,無法鬆弛某點 i ,但是路徑大小相同,說明當前路徑是最短路徑的一條!所以還要考慮,對“最短路徑”的最大人數進行維護。 如果 (到u點時能捎上的最大人數 + i點本身城市駐紮人數) > (當前記錄的 i 點能捎上的最大人數) ,就更新這個值。
如果 可以通過點u,鬆弛某點 i 時,那麼此點的最短路徑必然更新,如果最短路徑都更新了,那麼 最大人數 肯定也要 “覆蓋” !
3、 我寫的Dijkstra模板比較懶,寫完以後,跑出來的“最大人數”的結果一直都比樣例大。百思不得其解。觀摩了別人的AC代碼之後,才最後終於明白是因爲我的代碼裏,鬆弛的循環部分少了 一句 (if(!book(i))) ,加上就AC了。但是,爲什麼呢?
想了一會明白——我精簡這個模板的時候,可以刪去了這句話,是因爲,只討論最短路的Dijkstra時候,每一次被選中的鬆弛點,必然是沒有被標記過的剩下的點中,和起點距離最近的點!這是Dijkstra找 鬆弛點的算法本質。 而下一步的鬆弛循環中,用鬆弛點對鬆弛點本身進行鬆弛,是不會影響結果的!
但是,對最短路進行變形的時候,因爲引入了一個 “最大人數” 的維護與討論,在鬆弛階段,用鬆弛點對鬆弛點進行鬆弛的話,會導致 “誤判” 的,對 “最大人數” 的重新累加。就會導致結果翻倍!
也就是說,如果我把代碼的(if(!book(i))) 改成 (if( i!=u )) 也是可以的!因爲他們的目的都已經達到了,就是不對鬆弛點u重新鬆弛即可!
終於AC了,學到了學到了。我對Dijkstra還是不夠了解吶。
Code:
我的這個代碼中,用的就是if( i!=u )來防止鬆弛點重複鬆弛了,給自己一個警戒。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define inf 509
#define INF 0x3f3f3f3f
#define loop(x,y,z) for(x=y;x<z;x++)
int n,m,s,g;
int men[inf];//每個城市的救援人數
int path[inf][inf];//路徑矩陣
int book[inf],dis[inf],rescue[inf];//標記,最短距離,以及到i城市最多能集合多少救援人員
int num[inf];
void Input()
{
int i,j,u,v,t;
scanf("%d%d%d%d",&n,&m,&s,&g);
loop(i,0,n) //忘記輸入n後初始化了。。
loop(j,0,n)
path[i][j]=(i==j)?0:INF;
memset(book,0,sizeof book);
memset(num,0,sizeof num);//初始化方法數,起點爲1
num[s]=1;//起點到達方法數初始化爲1
loop(i,0,n)scanf("%d",&men[i]);
loop(i,0,m)
{
scanf("%d%d%d",&u,&v,&t);
path[u][v]=path[v][u]=t;
}
}
void Dijkstra()
{
int i,j,k,u,mini;
loop(i,0,n)dis[i]=path[s][i];
rescue[s]=men[s]; //要初始化
loop(j,0,n)
{
mini=INF;
loop(i,0,n)
if(!book[i]&&dis[i]<mini)
mini=dis[u=i]; //找最小
book[u]=1;//標記
//鬆弛
loop(i,0,n)// !book[i]
if(i!=u){
if(dis[i]>dis[u]+path[u][i])
{
dis[i]=dis[u]+path[u][i];//縮短邊
num[i]=num[u];
//如果當前的路徑是最短路,那麼路徑上的救援人數當然應重新賦值
//因爲以前的救援人數已經不是“最短的路徑”上的了
//救援隊最大隊伍,等於能到達U點的人數,加上I點城市自己的人數
rescue[i]=rescue[u]+men[i];
}
else if(dis[i]==dis[u]+path[u][i])
{
//如果通過u點到達i點的距離相同,
//那麼到達u點的方法數就會累加到到達i點的方法數上
num[i]+=num[u];
//如果當前路徑也是最小的,那麼就要判斷(維護)當前的最大救援人數值了
rescue[i]=max(rescue[i],rescue[u]+men[i]);
}
}
}
}
void Output()
{
printf("%d %d\n",num[g],rescue[g]);
}
int main()
{
Input();
Dijkstra();
Output();
return 0;
}