Bootstrap

PAT 甲级1003 Emergency 题解

在PAT1003 Emergency 这道题上上卡了很久,多次修改考虑漏掉的Case后,成功解决问题。

题目描述

每个输入文件包含一个测试用例。 对于每个测试用例,第一行包含4个正整数:N(≤500) - 城市数量(城市数量从0到N-1),M - 道路数量,C1和C2 - 城市 您当前在,并且您必须分别保存。 下一行包含N个整数,其中第i个整数是第i个城市中救援队的数量。 然后是M行,每行描述一个具有三个整数c1,c2和L的道路,它们分别是由道路和道路长度连接的一对城市。 保证存在从C1到C2的至少一条路径。

关键信息

  1. 图G是连通的无向图,没有孤立点
  2. 图中所有边权重都为正值,没有负圈存在的可能性
  3. 图中是有很多的圈

题目要点

  • 主要数据结构 采用邻接表(adjedges)存储图的信息,没有使用邻接矩阵考虑到稀疏矩阵下,邻接表效率更高,但对这道题其实后者可能更快。
     1.ArrayList[] adjedges 可以由每个点找到所有邻边
     2.ArrayList[] pathToBag 记录路径的包结构,记录所有最短路径中的前    继节点,为了得到最后的road[i]的信息。
     3.int[] distTo 标准Dijsktra节点路径长度记录,每次relax都需要这    用这个数据去做判断。初始化,每个节点disto[i]设为正无穷大,起点设为0.0
     4.Node[] vNodes 储存节点信息。节点权重即为RescueTeams[i]
private  class Node{
        int _No;
        int nodeWeight;
        Node(int No,int w){
            this._No=No;
            this.nodeWeight=w;
        }
    }

5.Queue queue 主要程序运行结构,用队列来添加和处理需要Rela x的节点,考虑先进先出的顺序。
 6.boolean[] Onqueue 标记每个节点是否在队列中。

解题主要思路

  1. Dijsktra方法,反复Relax图中的每一条边,直到没有需要Relax的边为止。
  2. 初始化图G的时候,起点城市的Road[i]置为1
  3. Relax图中的每一条边三种情况:v1是当前边的一点,通过theOther()拿到这条边的另外一点
      1.

distTo[e.theOther(v1)] > distTo[v1]+e.Edgeweight

此时,需要更新road[],PathToBag[]和rescueteam[]信息

  • Road[e.theOther(v1)] <-- 前一点的Road[v1]
  • PathToBag需要考虑这条边是不是第一次更新,如果是第一次,直接将该点的前继加入就好,如果不是,因为又重新找到了更短的路径,需要先删除之前保存的节点,再添加新节点。

if (pathToBag[e.theOther(v1)].size()==0){
pathToBag[e.theOther(v1)].add(v1);
}
else{
pathToBag[e.theOther(v1)].clear();
pathToBag[e.theOther(v1)].add(v1);
}

  • rescueTeams[e.theOther(v1)] = rescueTeams[v1]+vNodes[e.theOther(v1)].nodeWeight; 累加就可以

2.

distTo[e.theOther(v1)] == distTo[v1]+e.Edgeweight

同理,更新三个信息。

  • 此时某点遇到了相同的最短路径,一条路径上某点的前继有多于一个,所以统计road[]信息,需要又前继上每个点的和累加。如下,

int cnt =0;
for (int i = 0; i <pathToBag[e.theOther(v1)].size() ; i++) {
cnt += roads[pathToBag[e.theOther(v1)].get(i)];
}
roads[e.theOther(v1)] =cnt;

  • rescueTeams[e.theOther(v1)] = Math.max(rescueTeams[v1]+vNodes[e.theOther(v1)].nodeWeight,rescueTeams[e.theOther(v1)]);遇到路径相等的情况,rescueTeams取这些路径中点权重之和最大的一条
  • if (!pathToBag[e.theOther(v1)].contains(v1)) {
    pathToBag[e.theOther(v1)].add(v1);
    }
    一定要判断一下,包是否加入了这个节点,避免重复添加某个节点。
    3.

distTo[e.theOther(v1)] >distTo[v1]+e.Edgeweight

不需要做任何操作

代码如下:

package com.uestc;
import java.awt.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Emergency {
    public  boolean[] Onqueue;
    public  Queue<Integer> queue;
    private Node[] vNodes;
    private int[] distTo;
    private int[] rescueTeams;
    private int[] roads;
    //Arraylist建一个bag存放所有前继点
    //private int[] pathTo;
    private ArrayList<Integer>[]  pathToBag;
    private ArrayList<Edge>[] adjedges;
    //static int cnt=0;

    public Emergency(int V,int s){

        vNodes = new Node[V];
        distTo = new int[V];
        rescueTeams =new int[V];
        roads = new int[V];
        roads[s] = 1;
        queue = new LinkedList<Integer>();
        Onqueue = new boolean[V];
        //pathTo 记录到这个点的路径上前一个点
       // pathTo = new int[V];

        //路径背包存放所有前继点
        pathToBag = (ArrayList<Integer>[]) new ArrayList[V];
        for (int i = 0; i <V ; i++) {
            pathToBag[i] = new ArrayList<Integer>(V);
        }
        for (int i = 0; i <V ; i++) {
            distTo[i] = Integer.MAX_VALUE;

        }
        distTo[s] =0 ;
        adjedges = (ArrayList<Edge>[]) new ArrayList[V];
        for (int i = 0; i <V ; i++) {
            adjedges[i] =new ArrayList<Edge>();
        }
    }
    public void setNodesweight(int[] tNW){
        for (int i = 0; i <vNodes.length ; i++) {
            int w=tNW[i];
            vNodes[i] = new Node(i,w);
            rescueTeams[i]=w;
        }

    }
    public Iterable<Edge> adj(int v) {

        return adjedges[v];
    }

    private  class Node{
        int _No;
        int nodeWeight;
        Node(int No,int w){
            this._No=No;
            this.nodeWeight=w;
        }
    }
    public void setEdge(int s,int t,int w){
        Edge edge=new Edge(s,t,w);
        adjedges[s].add(edge);
        adjedges[t].add(edge);

    }
    private class Edge{
        int Edgeweight;
        int n1;
        int n2;

        Edge(int s,int t,int w){
            this.n1=s;
            this.n2=t;
            this.Edgeweight= w;

        }
        int theOther(int temp){
            if (temp == n1) {
                return n2;
            }else
                return n1;
        }

    }
    public void relax(int v1,Edge e){
        if (distTo[e.theOther(v1)] > distTo[v1]+e.Edgeweight) {
            distTo[e.theOther(v1)] = distTo[v1]+e.Edgeweight;
        if (pathToBag[e.theOther(v1)].size()==0){
            pathToBag[e.theOther(v1)].add(v1);
            }
        else{
            pathToBag[e.theOther(v1)].clear();
            pathToBag[e.theOther(v1)].add(v1);
        }
        if (Onqueue[e.theOther(v1)] == false) {
            queue.offer(e.theOther(v1));
            Onqueue[e.theOther(v1)] = true;
        }
        roads[e.theOther(v1)] = roads [v1];
        rescueTeams[e.theOther(v1)] = rescueTeams[v1]+vNodes[e.theOther(v1)].nodeWeight;
        }
        else if (distTo[e.theOther(v1)] == distTo[v1]+e.Edgeweight){
            if (Onqueue[e.theOther(v1)] == false) {
                queue.offer(e.theOther(v1));
                Onqueue[e.theOther(v1)] = true;
            }
            if (!pathToBag[e.theOther(v1)].contains(v1)) {
                pathToBag[e.theOther(v1)].add(v1);
            }

            //pathTo[e.theOther(v1)] = v1;
            rescueTeams[e.theOther(v1)] = Math.max(rescueTeams[v1]+vNodes[e.theOther(v1)].nodeWeight,rescueTeams[e.theOther(v1)]);
            int cnt =0;
            for (int i = 0; i <pathToBag[e.theOther(v1)].size() ; i++) {
                cnt += roads[pathToBag[e.theOther(v1)].get(i)];
            }
            roads[e.theOther(v1)] =cnt;



        }

    }
    public int getRoads(int i) {
        return roads[i];
    }

    public int getRescueTrams(int v){
        return rescueTeams[v];
    }

    public static void main(String[] args){
        int n,m,c1,c2;
        Scanner scanner = new Scanner(System.in);
        n= scanner.nextInt();
        m= scanner.nextInt();
        c1= scanner.nextInt();
        c2= scanner.nextInt();
        int[] Nw= new int[n];
        for (int i = 0; i <n; i++) {
            Nw[i]= scanner.nextInt();
        }
        Emergency my=new Emergency(n,c1);
        my.setNodesweight(Nw);
        int start,to,weight;
        for (int i = 0; i <m ; i++) {
            start= scanner.nextInt();
            to= scanner.nextInt();
            weight= scanner.nextInt();
            my.setEdge(start,to,weight);
        }
//        数据读入完成
//        如果 c1->c1
        if (c1 == c2) {
            System.out.print(1+" "+my.getRescueTrams(c2));
        }
        else {
           
            my.queue.offer(c1);
            my.Onqueue[c1] = true;
            //isvisit[c1]=true;
            while(!my.queue.isEmpty()){
                int num=my.queue.poll();
                my.Onqueue[num] = false;

                for (Edge e:my.adj(num)) {
                        my.relax(num,e);
                }
            }

            System.out.print(my.getRoads(c2)+" "+my.getRescueTrams(c2));
        }


    }
}

总结

  • 选用的这个结构,来自Bellford算法的Queue实现,对每条边根据邻边进行Relax操作。当某条边被Relax后,该边另外一个节点会被加入到Queue中,与这个点邻接的边会被重新更新,达到了动态更新的效果。
  • 另外还有 IndexMinPQ的经典结构,类似Prim生成树算法,采用贪心策略,总是能够处理当前情况下的权重最小的一条路径,很有借鉴意义。
  • 最后推荐一波《算法》这本书,相当经典。我的解题主要策略来自书中。

友情提示:容易出错的Case

  • 为各位准备了个容易出问题的Case,你的代码可以跑跑看结果对不

6 8 0 2
1 1 1 1 1 1
0 1 1
0 2 1000
0 3 4
0 4 3
1 5 1
4 5 1
3 4 1
2 3 2
结果应该是3 6

;