【算法练习】kuangbin-Acwing(简单搜索)

kuangbin-Acwing系列文章,记录了自己在练习过程中的想法和体会,可能不具有太大价值,大家选择性阅读。

一、棋盘问题(DFS-简单)

在这里插入图片描述
因为放了一行,那当前行就不能放了,可以以行为元素进行搜索,每次遍历当前行的所有列是否可以放,用数组记录列的使用情况。 需要注意可能存在当前行不放、当前列不放的情况。

import java.util.*;

public class Main {
    static int n;
    static char[][] map;
    static int count;
    static boolean[] col;
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        while (true) {
            n = scan.nextInt();
            int k = scan.nextInt();
            if (n == -1 && k == -1) break;
            map = new char[n][n];
            for (int i = 0; i < n; i++) {
                map[i] = scan.next().toCharArray();
            }
            count = 0;
            col = new boolean[n];
            dfs(0, k);
            System.out.println(count);
        }
    }
    public static void dfs(int row, int k) {
        if (k == 0) {
            count++;
            return;
        }
        if (row == n) return;
        // 搜索每一列
        for (int i = 0; i < n; i++) {
            // 判断当前列是否可用
            if (!col[i] && map[row][i] == '#') {
                col[i] = true;
                dfs(row + 1, k - 1);
                // 回溯
                col[i] = false;
            }
        }
        // 也可以当前行不放东西
        dfs(row + 1, k);
    }
}

二、地牢大师(BFS-简单)

在这里插入图片描述
考虑三维的BFS即可,还记得BFS模板的两种写法吗?一种直接把当前队列中的所有元素拿出来,一种是边遍历边入队。

import java.util.*;

class node {
    int x, y, z;
    node(){};
    node(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}
public class Main {
    static char[][][] map;
    // 方向数组
    static int[] xx = new int[] {1, 0, -1, 0, 0, 0};
    static int[] yy = new int[] {0, 1, 0, -1, 0, 0};
    static int[] zz = new int[] {0, 0, 0, 0, -1, 1};
    static boolean[][][] vis;
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        while (true) {
            int L = scan.nextInt();
            int R = scan.nextInt();
            int C = scan.nextInt();
            if (L == 0 && R == 0 && C == 0) break;
            map = new char[L][R][C];
            vis = new boolean[L][R][C];
            node b = new node();
            for (int i = 0; i < L; i++) {
                for (int j = 0; j < R; j++) {
                    map[i][j] = scan.next().toCharArray();
                    for (int k = 0; k < C; k++) {
                        if (map[i][j][k] == 'S') {
                            b = new node(i, j, k);
                        }
                    }
                }
            }
            // 是否到达终点
            boolean flag = false;
            Queue<node> queue = new LinkedList<>();
            queue.offer(b);
            int count = 0;
            while (!queue.isEmpty()) {
                if (flag) break;
                int size = queue.size();
                // 遍历当前队列中所有节点
                for (int i = 0; i < size; i++) {
                    node tmp = queue.poll();
                    vis[tmp.x][tmp.y][tmp.z] = true;
                    // 遍历6个方向
                    for (int j = 0; j < 6; j++) {
                        node temp = new node();
                        temp.x = tmp.x + xx[j];
                        temp.y = tmp.y + yy[j];
                        temp.z = tmp.z + zz[j];
                        if (temp.x < 0 || temp.y < 0 || temp.z < 0 || temp.x >= L || temp.y >= R || temp.z >= C) {
                            continue;
                        }
                        if (map[temp.x][temp.y][temp.z] == '#') continue;
                        if (vis[temp.x][temp.y][temp.z]) continue;
                        if (map[temp.x][temp.y][temp.z] == 'E') {
                            flag = true;
                            count++;
                            break;
                        }
                        // 新节点入队
                        vis[temp.x][temp.y][temp.z] = true;
                        queue.offer(temp);
                    }
                    if (flag) {
                        break;
                    }
                }
                // 完成整个一波的前进
                count++;
            }
            if (flag) {
                System.out.printf("Escaped in %d minute(s).\n", count - 1);
            } else {
                System.out.println("Trapped!");
            }
        }
    }
}

三、抓住那头牛(BFS-简单)

在这里插入图片描述
属于是一维的BFS,每次遍历三个可能的移动轨迹即可。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int k = scan.nextInt();
        boolean[] vis = new boolean[100001];
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(n);
        boolean flag = false;
        int count = 0;
        // 每次三种移动方式
        int[] step = new int[3];
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int cur = queue.poll();
                vis[cur] = true;
                step[0] = cur - 1;
                step[1] = cur + 1;
                step[2] = cur * 2;
                for (int j = 0; j < 3; j++) {
                    if (step[j] < 0 || step[j] > 100000) continue;
                    if (vis[step[j]]) continue;
                    if (step[j] == k) {
                        flag = true;
                        break;
                    }
                    vis[step[j]] = true;
                    queue.offer(step[j]);
                }
                if (flag) {
                    break;
                }
            }
            // 整体遍历完一波
            count++;
            if (flag) {
                break;
            }
        }
        System.out.println(count);
    }
}

四、费解的开关(状态压缩枚举-中等)

在这里插入图片描述
给你一个5x5的01矩阵,其中1代表灯on,0代表灯down,每次可以对某个位置的开关以及其上下左右的开关进行操作,使得它们的状态均改变,问能否只操作6个以内的开关使得全部的灯都亮。

属于搜索问题中的开关问题,本质是枚举,但不是对所有的开关都进行枚举。

解决思路: 1、枚举第一行的开关的所有操作情况,每个灯可以亮或者不亮,一行有5个灯,一共2 ^ 5种操作,这种很明显就是之前提到的状态压缩。

2、当我们发现对第一行进行了如上操作后,第二行的操作就固定了,只能去点亮第一行没亮的灯的下面的灯,因为第一行的其它灯的状态已经确定了,不能再去修改了,只能靠下面的灯的按动改变。,依次类推,第3,4行同样的操作,第5行是最后一行,不需要再进行操作,因为在处理第4行时,就按了第5行。

3、怎么判断全部都点亮了? 最后看一下最后一行的情况,如果最后一行刚好全部点亮,说明是可行的方案,如果最后一行最后有灯没有点亮,说明这种方案不行。还要注意方案数最后要在<=6的范围内。

注意题目中需要对数组进行复制和还原,要注意java的数组复制方式

import java.util.*;

public class Main {
    static char[][] map = new char[5][5];
    static int n;
    static int[] xx = new int[]{0,1,-1,0,0};
    static int[] yy = new int[]{0,0,0,1,-1};
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        while (n != 0) {
            int ans = Integer.MAX_VALUE;
            // 读取当前状态
            for (int i = 0; i < 5; i++) {
                map[i] = scan.next().toCharArray();
            }
            // 确定第一行所有开关的可能状态
            // 1就是把当前开关按一次
            for (int i = 0; i < (1 << 5); i++) {
                // 备份一份原始状态(注意java的数组复制方式)
                char[][] backUp = new char[5][5];
                for (int j = 0; j < 5; j++) {
                    backUp[j] = Arrays.copyOf(map[j], 5);
                }

                int step = 0;
                // 看第一行哪些需要按
                for (int j = 0; j < 5; j++) {
                    // 说明当前按钮要被按一次(不管亮不亮)
                    // 通过移位判断最低位是否为1
                    if (((i >> j) & 1) == 1) {
                        turn(0, j);
                        step++;
                    }
                }
                // 只需要看前4行
                for (int j = 0; j < 4; j++) {
                    // 5列
                    for (int k = 0; k < 5; k++) {
                        // 如果有没亮的,就去按动它下面一个按钮
                        if (map[j][k] == '0') {
                            step++;
                            turn(j + 1, k);
                        }
                    }
                }
                // 遍历完前4行,看最后一行是否全亮
                boolean flag = true;
                for (int j = 0; j < 5; j++) {
                    if (map[4][j] == '0') {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    ans = Math.min(ans, step);
                }
                // 恢复初始状态
                map = backUp;
            }
            if (ans > 6) System.out.println(-1);
            else System.out.println(ans);
            n--;
        }
    }
    public static void turn(int x, int y) {
        for (int i = 0; i < 5; i++) {
            int tx = x + xx[i];
            int ty = y + yy[i];
            if (tx < 0 || ty < 0 || tx >= 5 || ty >= 5) continue;
            // 异或
            map[tx][ty] ^= 1;
        }
    }
}

五、翻转(同上)

在这里插入图片描述
和上面方法一样,注意这两道题的本质还是状态压缩,还是要看数据量大小,如果过大肯定不能用。

import java.util.*;

public class Main {
    static int[][] map;
    static int m, n;
    static int[] xx = new int[]{0,1,-1,0,0};
    static int[] yy = new int[]{0,0,0,1,-1};
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        m = scan.nextInt();
        n = scan.nextInt();
        map = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                map[i][j] = scan.nextInt();
            }
        }
        int ans = Integer.MAX_VALUE;
        int[][] ansMet = new int[m][n];
        // 第一行可能的操作方案数:2 * 2 * 2... = 2 ^ 15
        // 遍历所有操作情况
        for (int i = 0; i < (1 << n); i++) {
            // 备份状态一定要写在循环内,因为后面的恢复是赋予索引
            // 备份初始状态
            int[][] backUp = new int[m][n];
            for (int j = 0; j < m; j++) {
                backUp[j] = Arrays.copyOf(map[j], n);
            }
            // 记录整体翻转次数
            int[][] cntMet = new int[m][n];
            int step = 0;
            // n列
            for (int j = 0; j < n; j++) {
                // 看具体第一行哪一列需要flip
                if (((i >> j) & 1) == 1) {
                    cntMet[0][j]++;
                    flip(0, j);
                    step++;
                }
            }
            // 遍历前m - 1行,最后再确定最后一行是否全0即可
            for (int j = 0; j < m - 1; j++) {
                // 每行n列
                for (int k = 0; k < n; k++) {
                    // 当前行为1,就把下面的一个元素翻转
                    if (map[j][k] == 1) {
                        cntMet[j + 1][k]++;
                        flip(j + 1, k);
                        step++;
                    }
                }
            }
            boolean flag = true;
            for (int j = 0; j < n; j++) {
                if (map[m - 1][j] == 1) {
                    flag = false;
                    break;
                }
            }
            if (flag && step < ans) {
                ans = step;
                // 记录翻转次数数组
                for (int j = 0; j < m; j++) {
                    ansMet[j] = Arrays.copyOf(cntMet[j], n);
                }
            }
            // 恢复初始状态
            map = backUp;
        }
        if (ans == Integer.MAX_VALUE) {
            System.out.println("IMPOSSIBLE");
        } else {
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    System.out.print(ansMet[i][j] + " ");
                }
                System.out.println();
            }
        }
    }
    public static void flip(int x, int y) {
        for (int i = 0; i < 5; i++) {
            int tx = x + xx[i];
            int ty = y + yy[i];
            if (tx < 0 || ty < 0 || tx >= m || ty >= n) continue;
            // 异或
            map[tx][ty] ^= 1;
        }
    }
}

六、找倍数(BFS-简单)

在这里插入图片描述
思路很简单,就是尝试当前位置0-1情况,关键是如何把代码写的足够美观简洁。题目中说任意方案都行,那我们就用BFS最快找到满足条件的即可。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        while (true) {
            int n = scan.nextInt();
            if (n == 0) break;
            Queue<Long> queue = new LinkedList<>();
            queue.offer(1L);
            while (!queue.isEmpty()) {
                long cur = queue.poll();
                if (cur % n == 0) {
                    System.out.println(cur);
                    break;
                }
                // 就这两种情况
                queue.offer(cur * 10);
                queue.offer(cur * 10 + 1);
            }
        }
    }
}

七、质数路径(BFS-简单)

在这里插入图片描述
刚开始是的思路是找最近的且相差只有一个数字的素数,但是速度很慢,可以换种思路:对于每个数只有4位,每位有10种更换的可能,所以总共每个数有40种可能,去除特殊情况(最高位=0,或与原数相同),并用vis数组记录使用过的素数(注意每次新的数据需要清空vis数组)。

再用一个distTo数组记录起点素数到某个数的操作次数。

btw,线性筛一定要熟练。

import java.util.*;

public class Main {
    public static int[] prime = new int[10001];
    public static boolean[] isPrime = new boolean[10001];
    public static boolean[] vis;
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        // 线性筛素数
        findPrime();
        while ((n--) > 0) {
            int a = scan.nextInt();
            int b = scan.nextInt();
            if (a == b) {
                System.out.println(0);
            } else {
                // 别忘了重置
                vis = new boolean[10001];
                // 最少次数BFS
                Queue<Integer> queue = new LinkedList<>();
                // 记录开始素数到某个素数的步数
                int[] distTo = new int[10001];
                distTo[a] = 0;
                queue.offer(a);
                boolean flag = false;
                while (!queue.isEmpty()) {
                    int size = queue.size();
                    for (int i = 0; i < size; i++) {
                        int cur = queue.poll();
                        vis[cur] = true;
                        // 四个位置,可以换成0-9,共40种情况
                        for (int j = 0; j < 10; j++) {
                            for (int k = 0; k < 4; k++) {
                                // 最高位不能为0
                                if (j == 0 && k == 0) continue;
                                int tmp = change(cur, j, k);
                                // 与自身相同不行
                                if (tmp == cur) continue;
                                // 已经遍历过的数
                                if (vis[tmp]) continue;
                                // 标记已访问
                                vis[tmp] = true;
                                if (tmp == b) {
                                    distTo[tmp] = distTo[cur] + 1;
                                    flag = true;
                                    break;
                                }
                                // 是素数
                                if (isPrime[tmp] == false) {
                                    queue.offer(tmp);
                                    distTo[tmp] = distTo[cur] + 1;
                                }
                            }
                            if (flag) break;
                        }
                    }
                    if (flag) break;
                }
                System.out.println(distTo[b]);
            }
        }
    }
    // 改变具体某一位
    public static int change(int old, int num, int site) {
        // site = 0修改高位,size = 3修改最低位
        int a = old % 10;
        int b = old / 10 % 10;
        int c = old / 100 % 10;
        int d = old / 1000 % 10;
        if (site == 0) {
            d = num;
        } else if (site == 1) {
            c = num;
            return d * 1000 + c * 100 + b * 10 + a;
        } else if (site == 2) {
            b = num;
        } else {
            a = num;
        }
        return d * 1000 + c * 100 + b * 10 + a;
    }
    // 线性筛
    public static void findPrime() {
        int count = 0;
        for (int i = 2; i < 10000; i++) {
            if (isPrime[i] == false) {
                prime[count++] = i;
            }
            // 除去合数
            for (int j = 0; j < count; j++) {
                if (prime[j] * i > 10000) break;
                isPrime[prime[j] * i] = true;
                // 保证每个合数只被最小的质因子分解
                if (i % prime[j] == 0) break;
            }
        }
    }
}

八、洗牌(BFS-简单)

在这里插入图片描述
在这里插入图片描述
模拟一下洗牌的过程,把每次洗牌洗好的结果放入队列中,用set记录当前洗牌结果是否被遍历过,直至最后找到相同的洗牌结果。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int t = scan.nextInt();
        int index = 0;
        while (index < t) {
            int c = scan.nextInt();
            char[] s1 = scan.next().toCharArray();
            char[] s2 = scan.next().toCharArray();
            String s3 = scan.next();
            StringBuilder str = new StringBuilder();
            // 先完成第一次洗牌,因为后续只需要把洗好的牌拆成两半再洗
            for (int i = 0; i < c; i++) {
                // S2在最底下
                str.append(s2[i]);
                str.append(s1[i]);
            }
            // 开始洗牌结果入队
            Queue<String> queue = new LinkedList<>();
            queue.offer(str.toString());
            Set<String> set = new HashSet<>();
            set.add(str.toString());
            // 记录洗牌次数
            int count = 1;
            boolean flag = false;
            while (!queue.isEmpty()) {
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    char[] tmp = queue.poll().toCharArray();
                    StringBuilder newStr = new StringBuilder();
                    // 再洗牌
                    for (int j = 0; j < c; j++) {
                        // 上半部分是新的s2
                        newStr.append(tmp[j + c]);
                        newStr.append(tmp[j]);
                    }
                    if (set.contains(newStr.toString())) continue;
                    if (newStr.toString().equals(s3)) {
                        flag = true;
                        break;
                    }
                    set.add(newStr.toString());
                    // 入队
                    queue.offer(newStr.toString());
                }
                // 整个完成一波
                count++;
                if (flag) {
                    break;
                }
            }
            if (flag) {
                System.out.printf("%d %d\n", index + 1, count);
            } else {
                System.out.printf("%d %d\n", index + 1, -1);
            }
            index++;
        }
    }
}

九、罐子(BFS-简单)

在这里插入图片描述
有三种操作,每种操作又有两种情况,共有六种情况。需要把ab容器的容量共同用于判断是否访问过,即二维数组vis。

注意class,StringBuilder,数组等,在赋值时出现引用的情况,这会很容易导致结果错误,因为后续的操作会改变已经保存下来的结果。

import java.util.*;

class node {
    int a, b;
    StringBuilder sb;
    node(){};
    node(int a, int b, StringBuilder sb) {
        this.a = a;
        this.b = b;
        this.sb = sb;
    }

}
public class Main {
    public static int a, b;
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        // a b是容积
        a = scan.nextInt();
        b = scan.nextInt();
        int c = scan.nextInt();
        int count = 0;
        // 把 a、b容量共同用来判断是否vis
        boolean[][] vis = new boolean[101][101];
        Queue<node> queue = new LinkedList<>();
        // 开始情况容量都为0,入队
        queue.offer(new node(0, 0, new StringBuilder()));
        vis[0][0] = true;
        boolean flag = false;
        StringBuilder ans = new StringBuilder();
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                // 遍历三种操作,每种操作有两种可能,共6种结果
                node temp = queue.poll();
                vis[temp.a][temp.b] = true;
                for (int j = 0; j < 3; j++) {
                    for (int k = 0; k < 2; k++) {
                        node tmp = work(j, temp.a, temp.b, new StringBuilder(temp.sb), k);
                        if (vis[tmp.a][tmp.b]) continue;
                        vis[tmp.a][tmp.b] = true;
                        // 满足题目条件
                        if (tmp.a == c || tmp.b == c) {
                            flag = true;
                            ans = new StringBuilder(tmp.sb);
                            break;
                        }
                        queue.offer(tmp);
                    }
                    if (flag) break;
                }
            }
            count++;
            if (flag) break;
        }
        if (flag) {
            System.out.println(count);
            System.out.println(ans.toString());
        } else {
            System.out.println("impossible");
        }
    }
    public static node work(int index, int aa, int bb, StringBuilder sb, int cnt) {
        int ta = aa;
        int tb = bb;
        if (index == 0) {
            // 装满水
            if (cnt == 0) {
                ta = a;
                return new node(ta, tb, sb.append("FILL(1)\n"));
            } else {
                tb = b;
                return new node(ta, tb, sb.append("FILL(2)\n"));
            }
        } else if (index == 1) {
            // 倒出水
            if (cnt == 0) {
                ta = 0;
                return new node(ta, tb, sb.append("DROP(1)\n"));
            } else {
                tb = 0;
                return new node(ta, tb, sb.append("DROP(2)\n"));
            }
        } else {
            // 把 a 倒入 b
            if (cnt == 0) {
                int need = b - tb;
                int have = ta;
                if (need != 0 && have != 0) {
                    if (have < need) {
                        ta = 0;
                        tb += have;
                    } else {
                        ta -= need;
                        tb = b;
                    }
                }
                return new node(ta, tb, sb.append("POUR(1,2)\n"));
            } else {
                int need = a - ta;
                int have = tb;
                if (need != 0 && have != 0) {
                    if (have < need) {
                        tb = 0;
                        ta += have;
                    } else {
                        tb -= need;
                        ta = a;
                    }
                }
                return new node(ta, tb, sb.append("POUR(2,1)\n"));
            }
        }
    }
}

十、点火游戏(BFS-简单)

在这里插入图片描述

在这里插入图片描述
想法很简单,记录下所有草地的坐标和草地的块数,暴力枚举两个草地点燃,如果草地只有一块直接打印0。

import java.util.*;

class node {
    int x, y;
    node(){};
    node(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
public class Main {
    // 方向数组
    static int[] x = new int[] {-1,1,0,0};
    static int[] y = new int[] {0,0,-1,1};
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int t = scan.nextInt();
        int index = 0;
        int n, m;
        char[][] map;
        boolean[][] vis;
        while (index < t) {
            n = scan.nextInt();
            m = scan.nextInt();
            map = new char[n][m];

            for (int i = 0; i < n; i++) {
                map[i] = scan.next().toCharArray();
            }
            // 记录所有草地下标
            int cnt = 0;
            int[] bx = new int[n * m];
            int[] by = new int[n * m];
            // 记录草地面积
            int originGrass = 0;
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    if (map[i][j] == '#') {
                        bx[cnt] = i;
                        by[cnt++] = j;
                        originGrass++;
                    }
                }
            }
            // 特别情况:一个草地
            if (originGrass == 1) {
                System.out.printf("Case %d: %d\n", index + 1, 0);
            } else {
                // 暴力枚举点燃两个草地
                boolean flag = false;
                int ans = Integer.MAX_VALUE;
                for (int i = 0; i < cnt; i++) {
                    node a = new node(bx[i], by[i]);
                    for (int j = i + 1; j < cnt; j++) {
                        Queue<node> queue = new LinkedList<>();
                        node b = new node(bx[j], by[j]);
                        queue.offer(a);
                        queue.offer(b);
                        // 每一次都要清空vis数组
                        vis = new boolean[n][m];
                        vis[bx[i]][by[i]] = true;
                        vis[bx[j]][by[j]] = true;
                        // 当前情况下所需时间
                        int count = 0;
                        // 当前情况下点燃的草地面积
                        int grassCnt = 2;
                        while (!queue.isEmpty()) {
                            int size = queue.size();
                            for (int k = 0; k < size; k++) {
                                node tmp = queue.poll();
                                vis[tmp.x][tmp.y] = true;
                                for (int l = 0; l < 4; l++) {
                                    int tx = tmp.x + x[l];
                                    int ty = tmp.y + y[l];
                                    if (tx < 0 || ty < 0 || tx >= n || ty >= m || vis[tx][ty] || map[tx][ty] != '#') {
                                        continue;
                                    }
                                    vis[tx][ty] = true;
                                    grassCnt++;
                                    queue.offer(new node(tx, ty));
                                }
                            }
                            // 全体都完成一波,时间++
                            count++;
                        }
                        // 全部点燃了
                        if (grassCnt == originGrass) {
                            flag = true;
                            ans = Math.min(ans, count);
                        }
                    }
                }
                // 当前一个case输入完成
                if (flag) {
                    System.out.printf("Case %d: %d\n", index + 1, ans - 1);
                } else {
                    System.out.printf("Case %d: %d\n", index + 1, -1);
                }
            }
            index++;
        }
    }
}

十一、起火迷宫(两次BFS-简单)

在这里插入图片描述
先用一次BFS记录火源到达各个可能点的时间,再用一次BFS去遍历乔可能走的点,能够到达该点的条件是:不是障碍、且到达该点的时间严格大于火源烧到这里的时间,只要找到有一次情况能到达出口即可break。注意判断特别情况:即一开始就在边缘位置,只用走一步即可出去。

import java.util.*;

class node {
    int x, y;
    node(){};
    node(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
public class Main {
    static int[] x = new int[] {1,-1,0,0};
    static int[] y = new int[] {0,0,1,-1};
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int t = scan.nextInt();
        char[][] map;
        // 记录火烧到可能地方的时间
        int[][] time;
        // 记录J跑到可能地方的时间
        int[][] run;
        while ((t--) > 0) {
            int r = scan.nextInt();
            int c = scan.nextInt();
            map = new char[r][c];
            time = new int[r][c];
            for (int i = 0; i < r; i++) {
                Arrays.fill(time[i], -1);
            }
            node b = new node();
            Queue<node> fireTime = new LinkedList<>();
            for (int i = 0; i < r; i++) {
                map[i] = scan.next().toCharArray();
                for (int j = 0; j < c; j++) {
                    if (map[i][j] == 'J') {
                        b = new node(i, j);
                    } else if (map[i][j] == 'F') {
                        fireTime.offer(new node(i, j));
                        // 火烧到这里的时间 = 0
                        time[i][j] = 0;
                    }
                }
            }
            // 求得每个可能位置,火烧到的时间
            while (!fireTime.isEmpty()) {
                int size = fireTime.size();
                for (int i = 0; i < size; i++) {
                    node tp = fireTime.poll();
                    for (int j = 0; j < 4; j++) {
                        int tx = tp.x + x[j];
                        int ty = tp.y + y[j];
                        if (tx < 0 || tx >= r || ty < 0 || ty >= c || time[tx][ty] != -1) continue;
                        if (map[tx][ty] == '#') continue; // 障碍物烧不到
                        // 火烧到这里的时间
                        time[tx][ty] = time[tp.x][tp.y] + 1;
                        fireTime.offer(new node(tx, ty));
                    }
                }
            }
            // 再看起点到达各个可能点的时间是否绝对小于火烧到这里的时间

            // 本来就处在边缘位置
            if (b.x == 0 || b.x == r - 1 || b.y == 0 || b.y == c - 1) {
                System.out.println(1);
            } else {
                run = new int[r][c];
                for (int i = 0; i < r; i++) {
                    Arrays.fill(run[i], -1);
                }
                run[b.x][b.y] = 0;
                Queue<node> person = new LinkedList<>();
                person.offer(b);
                // 是否有路可以走
                int ans = 0;
                boolean flag = false;
                while (!person.isEmpty()) {
                    int size = person.size();
                    for (int i = 0; i < size; i++) {
                        node tp = person.poll();
                        for (int j = 0; j < 4; j++) {
                            int tx = tp.x + x[j];
                            int ty = tp.y + y[j];
                            if (tx < 0 || tx >= r || ty < 0 || ty >= c || run[tx][ty] != -1) continue;
                            if (map[tx][ty] == '#') continue; // 人不能走到这里
                            run[tx][ty] = run[tp.x][tp.y] + 1;
                            // 跑到这里的时间大于等于火烧到的时间
                            if (run[tx][ty] >= time[tx][ty]) {
                                continue;
                            }
                            // 跑出去了
                            if (tx == 0 || tx == r - 1 || ty == 0 || ty == c - 1) {
                                ans = run[tx][ty] + 1;
                                flag = true;
                                break;
                            }
                            person.offer(new node(tx, ty));
                        }
                        if (flag) break;
                    }
                    if (flag) break;
                }
                if (flag) System.out.println(ans);
                else System.out.println("IMPOSSIBLE");
            }
        }
    }
}

其余的搜索题目都比较简单了,都可以套用上面的方法求解,就不再列举。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@u@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值