爆刷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 点能捎上的最大人数) ,就更新这个值

扫描二维码关注公众号,回复: 2649624 查看本文章

  如果 可以通过点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;
}

猜你喜欢

转载自blog.csdn.net/Hide_in_Code/article/details/81486411