问题描述
小M最近沉迷于一款叫《孢子》的游戏,游戏中,小M需要在一个星球上完成跑腿任务,提升自己。星球的地图可以抽象为一个n行m列的矩阵,小M每次可以往上下左右任意方向移动一格。若小M往地图边缘移动,他会出现在另一边。例如,小M在第m列时往右移动会出现在第1列;在第1行时往上移动会出现在第n行。
这个星球的地图由一个生成器生成,分为两种地形。地图的生成规则如下:
- 生成两个数列
a[1...n]和b[1...m],其中的值为0或1。 - 对于坐标
(i,j)的点,若a[i] == b[j],则该点为地形A,否则为地形B。
小M需要从一个起点(x1, y1)移动到终点(x2, y2)执行任务。小M想知道他最少需要跨越多少次地形来完成任务。
n表示地图的行数。m表示地图的列数。a是长度为n的列表,表示每一行的地形类型(值为0或1)。b是长度为m的列表,表示每一列的地形类型(值为0或1)。q表示任务的数量。array是一个二维列表,每个元素为[x1, y1, x2, y2],表示任务的起点和终点坐标。
测试样例
样例1:
输入:
n = 3 ,m = 4 ,a = [0, 1, 1] ,b = [0, 1, 1, 0] ,q = 2 ,array = [[2, 1, 3, 3], [2, 4, 2, 1]]
输出:[1, 0]
样例2:
输入:
n = 3 ,m = 4 ,a = [0, 1, 0] ,b = [0, 0, 1, 1] ,q = 2 ,array = [[1, 2, 2, 2], [2, 1, 2, 3]]
输出:[1, 1]
样例3:
输入:
n = 5 ,m = 5 ,a = [1, 0, 1, 0, 1] ,b = [0, 1, 0, 1, 0] ,q = 3 ,array = [[1, 1, 3, 3], [2, 5, 4, 2], [3, 4, 5, 1]]
输出:[4, 3, 3]
用DFS改进DP
一开始是想采用动态规划来解决这个题的,新建一个dp[n][m]的数组,并用BFS更新起点到每个点的追最少转换次数。但是在实践过程中发现如果某个点有多条路径可以到达,而且转换次数少的那一条路径的递归深度更大,因为这个点已经被递归深度较浅的那个处理了,所以要更新他的状态可能有点复杂。所以我们用DFS先遍历所有和当前节点地形一样的点,再会过来处理当前节点周围的点,这样的话BFS得到的dp在第一次遍历就是最小的了。
- 使用BFS(广度优先搜索)来遍历地图,因为BFS可以保证第一次到达某个点时,路径是最短的。
- 使用DFS(深度优先搜索)来预处理所有与当前地形相同的连续区域,以减少BFS的搜索范围。
import java.util.LinkedList;
import java.util.Queue;
public class Main {
public static int[] solution(int n, int m, int[] a, int[] b, int q, int[][] array) {
int[][] dp;//记录某个点到其他的点的跨越地形的最少次数
boolean[][] terrains=new boolean[n][m];//记录地形
int i,j;
//初始化地形信息
for(i=0;i<n;i++){
for(j=0;j<m;j++){
terrains[i][j]=(a[i]==b[j]);
}
}
boolean[][] visit;//记录图中的点有没有走过,因为先深度所以第一次肯定比第二次小,不用更新覆盖
Queue<int[]> que;//访问过的位置
int cnt=array.length,x,y;
int[] positions;
int[] res=new int[cnt];//结果
for(i=0;i<cnt;i++){
dp=new int[n][m];
visit=new boolean[n][m];
que=new LinkedList<>();
x=array[i][0]-1;
y=array[i][1]-1;
dp[x][y]=0;
visit[x][y]=true;
que.add(new int[]{x,y});
while(!que.isEmpty()){
//取出访问过的节点的坐标,然后像周围的没访问过的点扩展
positions=que.poll();
x=positions[0];
y=positions[1];
boolean terrain=terrains[x][y];
int now=dp[x][y];
//先深度优先找出所有和当前地形相同的连续的点
readTheSame(dp,que,terrains,visit,terrain,now,x,y,n,m);
//再来一次广度优先,只遍当前点周围的点
int up=x-1,down=x+1,left=y-1,right=y+1;
if(up<0)up+=n;
if(left<0)left+=m;
down%=n;
right%=m;
terrain=terrains[x][y];
now=dp[x][y];
if(!visit[up][y]){
if(terrains[up][y]!=terrain){
dp[up][y]=now+1;
}else{
dp[up][y]=now;
}
visit[up][y]=true;
que.add(new int[]{up,y});
}
if(!visit[down][y]){
if(terrains[down][y]!=terrain){
dp[down][y]=now+1;
}else{
dp[down][y]=now;
}
visit[down][y]=true;
que.add(new int[]{down,y});
}
if(!visit[x][left]){
if(terrains[x][left]!=terrain){
dp[x][left]=now+1;
}else{
dp[x][left]=now;
}
visit[x][left]=true;
que.add(new int[]{x,left});
}
if(!visit[x][right]){
if(terrains[x][right]!=terrain){
dp[x][right]=now+1;
}else{
dp[x][right]=now;
}
visit[x][right]=true;
que.add(new int[]{x,right});
}
}
res[i]=dp[array[i][2]-1][array[i][3]-1];
}
return res;
}
/**先深度优先找出所有和当前地形相同的连续的点
* @param dp 记录表,记录某个点到其他点最少的地形变换次数
* @param que 访问过的位置的记录
* @param terrains 地形
* @param visit 是否被访问
* @param terrain 当前地形
* @param now 到当前地形变换的次数
* @param x 当前的横坐标
* @param y 当前的纵坐标
* @param n 数组的行
* @param m 数组的列
*/
public static void readTheSame(int[][] dp,Queue<int[]> que,boolean[][] terrains,boolean[][] visit,boolean terrain,int now ,int x,int y,int n,int m){
int up=x-1,down=x+1,left=y-1,right=y+1;
if(up<0)up+=n;
if(left<0)left+=m;
down%=n;
right%=m;
if(!visit[up][y]){
if(terrains[up][y]==terrain){
visit[up][y]=true;
dp[up][y]=now;
que.add(new int[]{up,y});
readTheSame(dp,que,terrains,visit,terrain,now,up,y,n,m);
}
}
if(!visit[down][y]){
if(terrains[down][y]==terrain){
visit[down][y]=true;
dp[down][y]=now;
que.add(new int[]{down,y});
readTheSame(dp,que,terrains,visit,terrain,now,down,y,n,m);
}
}
if(!visit[x][left]){
if(terrains[x][left]==terrain){
visit[x][left]=true;
dp[x][left]=now;
que.add(new int[]{x,left});
readTheSame(dp,que,terrains,visit,terrain,now,x,left,n,m);
}
}
if(!visit[x][right]){
if(terrains[x][right]==terrain){
visit[x][right]=true;
dp[x][right]=now;
que.add(new int[]{x,right});
readTheSame(dp,que,terrains,visit,terrain,now,x,right,n,m);
}
}
}
public static void main(String[] args) {
// Add your test cases here
System.out.println(java.util.Arrays.equals(solution(3, 4, new int[]{0, 1, 1}, new int[]{0, 1, 1, 0}, 2, new int[][]{{2, 1, 3, 3}, {2, 4, 2, 1}}), new int[]{1, 0}));
System.out.println(java.util.Arrays.equals(solution(3, 4, new int[]{0, 1, 1}, new int[]{0, 0, 1, 1}, 2, new int[][]{{1, 2, 2, 2}, {2, 1, 2, 3}}), new int[]{1, 1}));
}
}