在PAT1003 Emergency 这道题上上卡了很久,多次修改考虑漏掉的Case后,成功解决问题。
题目描述
每个输入文件包含一个测试用例。 对于每个测试用例,第一行包含4个正整数:N(≤500) - 城市数量(城市数量从0到N-1),M - 道路数量,C1和C2 - 城市 您当前在,并且您必须分别保存。 下一行包含N个整数,其中第i个整数是第i个城市中救援队的数量。 然后是M行,每行描述一个具有三个整数c1,c2和L的道路,它们分别是由道路和道路长度连接的一对城市。 保证存在从C1到C2的至少一条路径。
关键信息
- 图G是连通的无向图,没有孤立点
- 图中所有边权重都为正值,没有负圈存在的可能性
- 图中是有很多的圈
题目要点
- 主要数据结构 采用邻接表(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 标记每个节点是否在队列中。
解题主要思路
- Dijsktra方法,反复Relax图中的每一条边,直到没有需要Relax的边为止。
- 初始化图G的时候,起点城市的Road[i]置为1
- 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