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