1003. Emergency (25)

题目链接:http://www.patest.cn/contests/pat-a-practise/1003

题目:

  
  
时间限制
400 ms
内存限制
65536 kB
代码长度限制
16000 B
判题程序
Standard
作者
CHEN, Yue

As an emergency rescue team leader of a city, you are given a special map of your country. The map shows several scattered cities connected by some roads. Amount of rescue teams in each city and the length of each road between any pair of cities are marked on the map. When there is an emergency call to you from some other city, your job is to lead your men to the place as quickly as possible, and at the mean time, call up as many hands on the way as possible.

Input

Each input file contains one test case. For each test case, the first line contains 4 positive integers: N (<= 500) - the number of cities (and the cities are numbered from 0 to N-1), M - the number of roads, C1 and C2 - the cities that you are currently in and that you must save, respectively. The next line contains N integers, where the i-th integer is the number of rescue teams in the i-th city. Then M lines follow, each describes a road with three integers c1, c2 and L, which are the pair of cities connected by a road and the length of that road, respectively. It is guaranteed that there exists at least one path from C1 to C2.

Output

For each test case, print in one line two numbers: the number of different shortest paths between C1 and C2, and the maximum amount of rescue teams you can possibly gather. All the numbers in a line must be separated by exactly one space, and there is no extra space allowed at the end of a line.

Sample Input
5 6 0 2
1 2 1 5 3
0 1 1
0 2 2
0 3 1
1 2 1
2 4 1
3 4 1
Sample Output
2 4

 

分析:

这一题是最短路径的扩展,题目要求是给你

第一行:城市数目(点的数目)和道路的数目(边的数目),你所在的城市(起点)和要营救的城市(终点)

第二行:每个城市所拥有的救援队伍数目(资源数目)

再接着就是“起点-终点-cost”的道路了

我们先来看最基本的最短路径,即所谓求s到t的最短路径(cost)值,在此基础上有的扩展为要1)记录路径(即记录各个路径上的各个点),2)记录相同最短cost的路径数目

3)包含上述两条都要,4)除cost之外,还有其他的开销或资源值,也就是说除了cost还要考虑其他的资源数目最优。等等。看完以下的介绍,你会发现扩展其实都是在Dijkstra算法的核心代码上进行添加或更改。

然后,我们接着来介绍两种求最短路径的方法,这里我是参考了王道考研机试丛书部分,里面讲得很清楚。

一)Floyd方法

求图上所有节点对之间的最短路径长度(矩阵存储节点之间的长度)

for(i = 1; i <= n; i++){
   for(j = 1; j <= n; j ++){
    ans[i][j] = -1;
   }
   ans[i][i] = 0;
  }//初始化


for(k = 1; k <= n; k++){
   for(i = 1; i <= n; i++){
    for(j = 1; j <= n; j ++){
     if(ans[i][k] == -1 || ans[k][j] == -1)continue;
     if(ans[i][j] == -1 || ans[i][j] > ans[i][k] + ans[k][j])
      ans[i][j] = ans[i][k] + ans[k][j];
    }
   }
  }//算法核心

二)Dijkstra算法

只能求得特定节点到其他所有节点的最短路径长度,即单源最短路径问题
(链接链表存储)

步骤:
1)初始化,集合K中加入节点1,节点1到节点1最短距离为0,到其他节点距离为无穷
2)遍历与集合K中节点直接相邻的边(U,V,C),其中U属于集合K,V不属于集合K,计算由节点1出发按照已经得到的最短路到达U,再由U经过该边到达V时的路径长度。比较所有与集合K中节点直接相邻的非集合K节点该路径长度。其中路径长度最小的节点被确定为下一个最短路径确定的节点。其最短路径长度即为这个路径长度,最后将该节点加入集合K。
3)若集合K中已经包含了所有的点,算法结束,重复步骤2.

#include<iostream>
#include<string.h>
using namespace std;
struct E{
 int next;
 int c;
};
vector<E> edge[101];//存储各个点的边集
bool mark[101];//表示是否已经加入到已知点的集合中
int Dis[101];//表示距离
int main(){
 int i,j,n,m;
 while(scanf("%d",&n) != EOF){
  if(n == 0 && m == 0)break;
  for(i = 1; i <= n; i++)edge[i].clear();
 
  //初始化
  while(m --){
   int a,b,c;
   scanf("%d%d%d",&a,&b,&c);
   E tmp;
   tmp.c = c;
   tmp.next = b;
   edge[a].push_back(tmp);
   tmp.next = a;
   edge[b].push_back(tmp);
  }
  for(i = 1; i <= n; i++){
   Dis[i] = -1;
   mark[i] = false;
  }
  Dis[1] = 0;
  mark[1] = true;
  int newP = 1;   //把起点作为新的点(这里是把1作为起点)
 
  //算法核心
  for(i = 1; i < n; i++){
   for(j = 0; j < edge[newP].size(); j ++){
    int t = edge[newP][j].next;
    int c = edge[newP][j].c;
    if(mark[t] == true)continue;
    if(Dis[t] == -1 || Dis[t] > Dis[newP] + c) //这是核心的部分,以上提到的扩展大多都是在这里做文章
     Dist[t] = Dis[newP] + c;                 //更新新点所连接点的距离
   }                                         
   int min = 123123123;
   for(j = 1; j <=n; j++){
    if(mark[j] == true)continue;
    if(Dis[j] == -1)continue;
    if(Dis[j] < min){
     min = Dis[j];
     newP = j;       //再次寻找未知的最短的距离点,把此点作为新的点进行迭代
    }
   }
   mark[newP] = true;
  }
  printf("%d\n",Dis[n]);
 }
 return 0;
}
本题中,有两个部分不同,第一是要得到最短路径的路径数目,第二是除了路径的cost还需要考虑救援队伍的数目;也就是说我们要在路径最短且相同的情况下选择救援队伍最多的那条,并且让记录路径条数的paths[]累加(注意不是递增,而是把之前点的paths给叠加上去)

所以更改Dijkstra算法的核心代码如下:

    if(Dis[t] == -1 || Dis[t] > Dis[newP] + c){
     paths[t] = paths[newP];<span style="white-space:pre">				</span>//paths[]是记录起点到各个点的最短路径数目的
     Dis[t] = Dis[newP] + c;
     all_nums[t] = all_nums[newP] + nums[newP];          //all_nums[]是记录从起点到当前点的总救援队伍数目的
    }<span style="white-space:pre">							</span>//nums[]是当前城市(点)的救援队伍数目的
    else if(Dis[t] == Dis[newP] + c){
     paths[t] += paths[newP];<span style="white-space:pre">				</span>//如果最短路径cost相同,则叠加上去,相同于说0到1有a1条,0到2有a2条,且1到3和2到3cost相同
     if(all_nums[t] < all_nums[newP] + nums[newP])<span style="white-space:pre">	</span>//那么起点(假设为0)到3就有a1 + a2条
      all_nums[t] = all_nums[newP] + nums[newP];<span style="white-space:pre">	</span>//救援队伍数目也要叠加
    }
   }

AC代码:

#include<stdio.h>
#include<vector>
using namespace std;
struct Edge{
 int next;
 int c;
};
vector<Edge> edge[502];
int Dis[502];
bool mark[502];
int paths[502];  //paths[]是记录起点到各个点的最短路径数目的
int nums[502];   //nums[]是当前城市(点)的救援队伍数目的
int all_nums[502];   //all_nums[]是记录从起点到当前点的总救援队伍数目的
int main(void){
 int i,j,n,m,s,e;
 while(scanf("%d %d %d %d",&n,&m,&s,&e) != EOF){
  for(i = 0; i < n; i ++){
   edge[i].clear();
   Dis[i] = -1;
   mark[i] = false;
   paths[i] = 0;
   nums[i] = 0;
   all_nums[i] = 0;
   scanf("%d",&nums[i]);
  }
  for(i = 0; i < m; i++){
   int a,b,c;
   scanf("%d %d %d",&a,&b,&c);
   Edge tmp;
   tmp.c = c;
   tmp.next = b;
   edge[a].push_back(tmp);
   tmp.next = a;
   edge[b].push_back(tmp);
  }
  Dis[s] = 0;
  mark[s] = true;
  int newP = s;
  paths[newP] = 1;<span style="white-space:pre">	</span>//这里是把s当作最初点
  for(i = 0; i < n; i++){
   for(j = 0; j < edge[newP].size(); j ++){
    int t = edge[newP][j].next;
    int c = edge[newP][j].c;
    if(mark[t] == true) continue;
    if(Dis[t] == -1 || Dis[t] > Dis[newP] + c){ 
     paths[t] = paths[newP];	 //paths[]是记录起点到各个点的最短路径数目的
     Dis[t] = Dis[newP] + c;
     all_nums[t] = all_nums[newP] + nums[newP];          //all_nums[]是记录从起点到当前点的总救援队伍数目的
    }	 //nums[]是当前城市(点)的救援队伍数目的
    else if(Dis[t] == Dis[newP] + c){
     paths[t] += paths[newP];	 //如果最短路径cost相同,则叠加上去,相同于说0到1有a1条,0到2有a2条,且1到3和2到3cost相同
     if(all_nums[t] < all_nums[newP] + nums[newP])	//那么起点(假设为0)到3就有a1 + a2条
      all_nums[t] = all_nums[newP] + nums[newP];	//救援队伍数目也要叠加
    }
   }
   int min = 1 << 30;
   for(j = 0; j < n; j ++){
    if(mark[j] == true) continue;
    if(Dis[j] == -1) continue;
    if(Dis[j] < min){
     min = Dis[j];
     newP = j;
    }
   }
   mark[newP] = true;
  }
  printf("%d %d\n",paths[e], all_nums[e] + nums[e]);
 }
 return 0;
}


Dijkstra算法一定要好好理解,特别是其中的核心代码部分,从中延伸的东西很多,也很复杂。

建议读者自己多写几遍。

另外,再把王道论坛的机试指南放在这边,当初我也是在王道论坛里下载的,里面图论部分最短路径部分有相应Floyd和Dijkstra算法的介绍,很详细。(不知道这样是否合适,如果有对王道不好的地方请及时联系我删除)王道论坛链接:http://www.cskaoyan.com/

http://yunpan.cn/cjs6VLfpVbvi9  访问密码 794e

另外,我看了一下,csdn上似乎没有对Dijkstra写的太直白的文章,读者可以去看一下

http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html

这一篇,举的例子和图解比较清楚。

——Apie陈小旭

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值