题目
试题 F: 狡兔 k 窟
时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分
【问题描述】
一只兔子名叫小蓝,它异常狡猾,在土中挖了若干洞窟并且设置了很多出
入口来应对紧急情况。它一共有 n 个通往地面的出入口,在地面上这 n 个出入
口之间由 n − 1 条长度为 1 的双向通路连成一个连通图。第 i 个出入口属于第 ci
个洞窟,因此小蓝可以在任意一个属于 ci 的出入口从地面进入洞窟然后从任意
一个属于 ci 的出入口跑出到达地面。
小蓝提出了 m 个逃跑路线,第 i 个路线希望从出入口 si 逃往 ti ,它希望在
逃跑的过程中在地面上跑动的距离尽可能短,请为每条路线计算逃跑时在地面
上跑动的最短距离。
【输入格式】
输入的第一行包含两个正整数 n, m ,用一个空格分隔。
第二行包含 n 个正整数 c1, c2, · · · , cn ,相邻整数之间使用一个空格分隔。
接下来 n − 1 行,第 i 行包含两个整数 ui
, vi ,用一个空格分隔,表示地面
上的一条通路连接 ui 和 vi 。
接下来 m 行,第 i 行包含两个整数 si
, ti ,用一个空格分隔。
【输出格式】
输出 m 行,每行包含一个整数,依次表示每个询问的答案。
【样例输入】
6 3
1 3 2 1 2 3
1 2
1 3
2 4
2 5
3 6
2 6
3 2
4 3
【样例输出】
0
1
1
【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n, m, ci ≤ 100 ;
对于所有评测用例,1 ≤ n, m, ci ≤ 5000 ,1 ≤ ui, vi, si, ti ≤ n 。
题目有点绕,不过容易看出这是图的最短路径的问题,最先想到的应该是深度搜索和广度搜索两种办法,这里就用深度搜索的办法解决(因为其他的我不会)。
先分析一下问题:
第 i 个出入口属于第 ci个洞窟,因此小蓝可以在任意一个属于 ci 的出入口从地面进入洞窟然后从任意一个属于 ci 的出入口跑出到达地面。
这句话是什么意思呢?
题目想表达的其实是一个洞可以有多个出口,出口与出口之间有通路,而题目的要求是给定两个点,找出两个点的最短路径,根据题目的条件,实际我们需要找的是洞与洞之间的最短路径,
可以先看一下测试样例
样例的第三个要查找4到3的最短路线,当前题目给出的条件为1 3 2 1 2 3·,第i个数为第i个出入口,而a[i]则为洞口编号,可以看出第1个和第4个入口在同一个洞,第2个和第6个在同一个洞,第3个和第5个在同一个洞。
入口与入口之间的通路为
因为是无向图,箭头应该是双向的(我懒得改了)
再举个例子:
假如要从入口1点到入口3的常规路线 是1->2->4->3 ,那那一共要走4步
但是如果有个入口5和入口3在同一个洞口,并且存在一条1->5的路,那么最短路线就变成了1->5
只需要走一步就可以到目标点了,因为5和3通向的是同一个洞,那么只需要到达入口5就相当于同时到达入口3了
看似是点与点之间的路线,实际是要查找洞与洞之间的最短路线
那么深度搜索的结束条件就出来了,就是如果当前遍历的点所在的洞和终点所在的洞是同一个时就计算结果并退出。
在变量方面,需要两个哈希表,一个存每个点对应着哪个洞,另一个存一个洞连接着哪个点,地图就用二维数组构建,要定义一个book数组记录每个点是否已经被访问过(防止出现无限递归)。
接下来就看代码实现
import java.util.*;
import java.math.*;
public class Main {
static Scanner in = new Scanner(System.in);
static int n = in.nextInt(), m = in.nextInt();
static int[][] map = new int[n+1][n+1];
static HashMap<Integer,ArrayList<Integer>> dong = new HashMap<>(); //记录每个洞连接着哪些点
static HashMap<Integer,Integer> dian = new HashMap<>(); //记录每个点连着哪些洞
static boolean[][] book = new boolean[n+1][n+1]; //标记当前点是否来过
static int min_step ; //记录最小步数
public static void main(String[] args) {
int[] temp = new int[n+1]; //用来构建图的临时数组,存放当前洞对应的点的编号
for (int i = 1; i <= n; i++) {
temp[i] = in.nextInt();
}
for (int i = 0; i < n-1; i++) {
int a = in.nextInt();
int b = in.nextInt();
map[a][b] = 1;
map[b][a] = 1; //因为是无向图,两个方向都要构建
dian.put(a,temp[a]);
dian.put(b,temp[b]); //存入当前两个点对应的洞
if(dong.get(temp[a]) == null){
ArrayList<Integer> list = new ArrayList<>();
list.add(a);
dong.put(temp[a],list);
}else{
dong.get(temp[a]).add(a);
}
if(dong.get(temp[b]) == null){
ArrayList<Integer> list = new ArrayList<>();
list.add(b);
dong.put(temp[b],list);
}else{
dong.get(temp[b]).add(b);
}
//将这两个点加入洞的对应关系中,如果不存在则创建一个新的集合并添加
}
for (int i = 0; i <m ; i++) {
int start = in.nextInt();
int end = in.nextInt();
min_step = Integer.MAX_VALUE; //记录最小步数
dfs(start,end,0); //传入起始点和终点,以及当前步数
System.out.println(min_step);
}
}
static void dfs(int a,int b ,int step){
if(Objects.equals(dian.get(a), dian.get(b))){ //如果当前点和终点在同一个洞,则计算最短路径
min_step = Math.min(step,min_step);
return;
}
if(!book[a][b]){
book[a][b] = true; //判断当前路线是否已经走过
}else{
return;
}
for (int i:
dong.get(dian.get(a))) { //必须把当前点所在的洞的所有点遍历
for (int j = 1; j <=n ; j++) {
if(map[i][j] == 1){ //遍历当前点连接的所有路径
dfs(j,b,step+1);
}
}
}
book[a][b] = false; //退回上一层递归的时候记得把book还原
}
}