小M的星球时代冒险(用DFS改进DP)

184 阅读5分钟

问题描述

小M最近沉迷于一款叫《孢子》的游戏,游戏中,小M需要在一个星球上完成跑腿任务,提升自己。星球的地图可以抽象为一个nm列的矩阵,小M每次可以往上下左右任意方向移动一格。若小M往地图边缘移动,他会出现在另一边。例如,小M在第m列时往右移动会出现在第1列;在第1行时往上移动会出现在第n行。
这个星球的地图由一个生成器生成,分为两种地形。地图的生成规则如下:

  • 生成两个数列a[1...n]b[1...m],其中的值为01
  • 对于坐标(i,j)的点,若a[i] == b[j],则该点为地形A,否则为地形B。

小M需要从一个起点(x1, y1)移动到终点(x2, y2)执行任务。小M想知道他最少需要跨越多少次地形来完成任务。

  • n 表示地图的行数。
  • m 表示地图的列数。
  • a 是长度为n的列表,表示每一行的地形类型(值为01)。
  • b 是长度为m的列表,表示每一列的地形类型(值为01)。
  • 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}));
    }
}