城市间紧急救援

1003 Emergency (25 分)
 

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 Specification:

Each input file contains one test case. For each test case, the first line contains 4 positive integers: N (≤) - the number of cities (and the cities are numbered from 0 to N1), 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 Specification:

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

题目大意为:

城市间紧急救援

作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。

输入格式:

输入第一行给出4个正整数N、M、S、D,其中N(2N500)是城市的个数,顺便假设城市的编号为0 ~ (N1);M是快速道路的条数;S是出发地的城市编号;D是目的地的城市编号。

第二行给出N个正整数,其中第i个数是第i个城市的救援队的数目,数字间以空格分隔。随后的M行中,每行给出一条快速道路的信息,分别是:城市1、城市2、快速道路的长度,中间用空格分开,数字均为整数且不超过500。输入保证救援可行且最优解唯一。

输出格式:

第一行输出最短路径的条数和能够召集的最多的救援队数量。数字间以空格分隔,输出结尾不能有多余空格。

注释:此题也可求(第二行输出从S到D的路径中经过的城市编号)。

题目分析:在此题中大家肯定也能看出来时求最短路径问题,只是加入了两个新的条件,一个是求出最短路径不唯一时的可以聚集的最大救援人数,二是求出最短路径的条数。

最基本的求出最短路径问题,我用的是Dijkstra算法求单源最短路径,回顾一下,大意为每次求出不在顶点集U中的到起始点最近的一个顶点,即为求dist[]中的最小值,加入到顶点集中,并进行松弛操作,更新不在顶点集U中的其他店的最短路径,直到遇到终止点时退出。

其次是在求最短路径不唯一时的情况,最短路径不唯一时,即为在每次向U中添加顶点时,距离起始点的最短路径的顶点不唯一,此时可以选择多个顶点,因为要求救援人数最大,所以我们选择救援人数最多的顶点加入到点集U中,并更新此点的聚集数组(juji[]),在进行松弛操作时,不能忘记要同时更新聚集数组和path数组。

在求最短路径的个数时,我们选择一个递归函数,基本思想为从end顶点(目的城市)开始,依次向上一级递归,直到递归到begin顶点(出发城市),因为前面我们已经求得了最短路径,如果与end顶点相连的上一顶点(设为u)的最短路径与u-end的距离之和为end顶点的最短路径(简而言之就是如果begin顶点到end顶点的最短路径已知,那么最短路径中begin到end的上一顶点u(end之前经过的城市)的最短路径与u到end的距离肯定为begin到end的最短路径),再将上一顶点u作为end顶点向上递归,直到递归到begin顶点位置,此时便有一条最短路径产生了,因为要对每个与end顶点相连的顶点进行判断,所以求出的最短路径也不唯一,每次到begin顶点令sum++,最后的sum即为最短路径的个数。

解释:在本题样例中,0到2的最短路径有两条,分别是0直接到2和0先到1再到2,我们先从2找与其相连的边(通过遍历顶点),先找到0,判断0到0的最短路径(0)加上0到2的最短路径(2)是否为0到2的最短路径(2),发现是相等的,于是找0的上一顶点,但是0已经是begin顶点了,所以最短路径个数加1,return。紧着进行循环找下一个与2相连的顶点,找到1,判断0到1的最短路径(1)和1到2的长度之和(1)是否为0到2的最短路径(2),结果是对的,接着开始判断1顶点,找与1相连的顶点,找到0,判断0到0的距离与0到1的距离之和是否为0到1的最短路径,结果是对的,于是又到了顶点0,sum++,此刻sum等于2,回到循环,2找到相连的点4,0到4的最短路径为2,4到2的路径为1,2加1不等于0到2的最短路径2,所以不考虑4,此刻所有与2相连的顶点全部判断完毕,递归结束,sum的值即为最短路径个数。

代码如下:

import java.util.*;
public class Main {
    /*以下为全局静态变量*/
    static int n;//顶点个数
    static int m;//边的个数
    static int begin;//起始点
    static int end;//终止点
    static int w[];//存放每个顶点的救援人数
    static int e[][];//邻接矩阵
    static int sum=0;//记录最短路径个数
    static int dist[];//Dijkstra算法中存放顶点最短路径
    static int path[];//Dijkstra算法中存放上一顶点
    static int juji[];//存放从起始点到此顶点处聚集的救援人数
    static int inf=999999;//在此令最大值为inf
    public static void main(String args[])
    {
        Scanner sc=new Scanner(System.in);
        n=sc.nextInt();//读入顶点
        m=sc.nextInt();//读入边
        begin=sc.nextInt();//读入起始顶点
        end=sc.nextInt();//读入结束顶点
        w=new int[n];
        e=new int[n][n];
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            {
                e[i][j]=inf;//矩阵初始化
            }
        }
        for(int i=0;i<n;i++)
        {
            w[i]=sc.nextInt();//读入每个顶点的救援人员数目
        }
        for(int i=0;i<m;i++)
        {
            int x1=sc.nextInt();
            int x2=sc.nextInt();
            e[x1][x2]=e[x2][x1]=sc.nextInt();//无向图:对称
        }
        dij();//Dijkstra算法
        dfs(end);//求最短路径算法
        System.out.print(sum+" "+juji[end]);//输出最短路径个数以及最大救援人数
    }
    static void dij()
    {
        dist=new int[n];
        juji=new int[n];
        path=new int[n];
        int s[]=new int[n];//记录是否加入顶点集U中
        for(int i=0;i<n;i++)
        {
            dist[i]=e[begin][i];//初始化dist数组为起始点到各个顶点的路径
            if(e[begin][i]<inf)
                path[i]=begin;//如果不为最大值即为存在边,所以此顶点的父顶点为起始点
            else
                path[i]=-1;//如果不存在边,另此顶点的父顶点为-1
        }
        dist[begin]=0;//起始点到自身为0,为什么要专门设置起始点到自身,原因是e矩阵在初始化时都为最大值,dist[begin]也是最大值,在求最短路径的个数时,递归到起始点时起始点如果为最大值就没办法求解了。
        s[begin]=1;//将起始点加入顶点集U中
        path[begin]=0;//起始点的父顶点为0
        juji[begin]=w[begin];//起始点聚集的救援人员为自身
        for(int i=0;i<n;i++)//每一次循环求出一个起始点到随机点的最短路径
        {
            int min=inf;//min记录最短路径
            int max=0;//max记录最短路径不唯一时最大救援人数
            for(int j=0;j<n;j++)
            {
                if(s[j]==0&&dist[j]<min)
                    min=dist[j];//找出一个未加入顶点集U的距离U最短的顶点
            }
            if(min==inf) break;//如果未找到为非连通图,退出
            int u=-1;//u记录最短路径不唯一时最大救援人员个数的顶点下标
            for(int j=0;j<n;j++)
            {
                if(s[j]==0&&dist[j]==min)//遍历顶点,未加入顶点集的点且dist[]=min
                {
                    int juji1=juji[path[j]]+w[j];//求出这个点的父顶点的聚集人数加上这个点的人数
                    if(juji1>max)//如果大于max
                    {
                        max=juji1;//最大救援人数更新
                        u=j;//最优下标更新
                    }
                        
                }
            }
            juji[u]=juji[path[u]]+w[u];//更新此刻u下标的聚集人数
            s[u]=1;//将u下标顶点加入到U中
            for(int j=0;j<n;j++)//松弛操作,每加入一个顶点(u)更新其他与u顶点有边且满足下面条件的点
            {
                int d=min+e[u][j];//对u。j有边的有意义,下一句一样
                int c=max+w[j];
                if(s[j]==0&&d<dist[j])//如果此点不在顶点集并且经过点u得到的距离小于之前的距离,更新
                {
                    path[j]=u;//父顶点改为u
                    dist[j]=d;
                    juji[j]=c;
                }
                else if(s[j]==0&&d==dist[j]&&c>juji[j])//如果距离相等但是救援人数增加了,更新juji[]
                {
                    juji[j]=c;
                    path[j]=u;//仍然修改父节点
                }
                    
            }
            if(s[end]==1)//如果最短路径到达end顶点,程序结束
            {
                /*以下代码为求最短路径对应的起始顶点到结尾顶点经过的顶点*/
//                int e=end;
//                int ee[]=new int[n+1];//在此设立一个数组存放依次的父节点
//                int k=n;
//                while(e!=begin)//如果e不到起始点
//                {
//                    ee[k]=e;//将e加入到数组尾部
//                    e=path[e];//e更新为e的父顶点
//                    k--;//数组尾部减一
//                }
//                for(int ii=k;ii<=n;ii++)//正向输出数组
//                {
//                    System.out.print(ee[ii]+" ");
//                }
//                System.out.println(juji[end]);//最短路径最大聚集
                break;
            }
        }
    }
    static void dfs(int k)
    {
        if(k==begin)//出口条件,如果到达起始顶点,路径条数加1,返回
        {
            sum++;
            return;
        }
            
        for(int i=0;i<n;i++)//每次遍历顶点
        {
            if(e[k][i]<inf&&i!=k)//如果存在边
            {
                if(dist[i]+e[k][i]==dist[k])//判断这条边对应的另一顶点的最小路径加上这条边的长度是否为此顶点的最短路径
                {
                    dfs(i);//如果满足,即为一条路径,向上找上一个顶点
                }
            }
        }
    }

}

此题的难点在于判断最短路径不唯一时的最大救援人数,要加入父顶点数组path[],判断时根据父顶点的最大救援人数加上自身是否为最大,为最大时选择此顶点。还有在判断最短路径个数时采用倒推的方法,从尾顶点倒推到起始顶点,如果能到起始顶点,便多一条最短路径。

空吧哇~

2019-07-30

 

猜你喜欢

转载自www.cnblogs.com/rousong/p/11272424.html