爆刷PAT(甲級)——之【1003】 Emergency (25)——最短路簡單變形

題意:   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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章