跳至主要內容

LCCUP'23 力扣杯春季编程大赛-战队赛/个人赛

大约 12 分钟

LCCUP'23 力扣杯春季编程大赛-战队赛/个人赛

战队赛

比赛时间 2023-05-07。感谢队友 @kexp@yuekunwang,喜提 Rank 84,超过了 97.8% 的选手!

T1. 符文储备(2 分)

解题思路

观察到值域比较小,预处理每个数的频次,然后线性遍历取最大连续和即可。

时间复杂度:O(n)

参考代码

class Solution {
    private static final int MAX_N = (int) (1e4 + 5);

    public int runeReserve(int[] runes) {
        int[] cnt = new int[MAX_N];
        for (int x : runes) {
            cnt[x]++;
        }

        int sum = 0, max = 0;
        for (int x = 0; x < MAX_N; x++) {
            if (cnt[x] > 0) {
                sum += cnt[x];
                max = Math.max(max, sum);
            } else {
                sum = 0;
            }
        }
        return max;
    }
}

T2. 城墙防线(4 分)

解题思路

结果显然具有单调性,二分答案即可。

时间复杂度:O(nlogn)

参考代码

class Solution {
    public int rampartDefensiveLine(int[][] rampart) {
        int n = rampart.length;

        // [1, n-2] 个城墙中,能膨胀的最小值(即为二分答案的上界)
        int min = Integer.MAX_VALUE;
        for (int i = 1; i < n - 1; i++) {
            int l = rampart[i][0] - rampart[i - 1][1];
            int r = rampart[i + 1][0] - rampart[i][1];
            min = Math.min(min, l + r);
        }

        int left = 1;
        int right = min + 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            // 边界二分 F, F,..., F, [T, T,..., T]
            // ----------------------^
            if (!checkMid(rampart, mid)) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left - 1;
    }

    // 时间复杂度 O(n) 校验能否膨胀 mid 个长度
    private boolean checkMid(int[][] rampart, int mid) {
        int n = rampart.length;
        // 上一个城墙膨胀的右端点
        int last = rampart[0][1];
        for (int i = 1; i < n - 1; i++) {
            int l = rampart[i][0] - last;
            // Math.max(0, mid - l) 为右侧膨胀距离,注意非负
            last = rampart[i][1] + Math.max(0, mid - l);
            if (last > rampart[i + 1][0]) {
                return false;
            }
        }
        return true;
    }
}

T3. 提取咒文(6 分)

解题思路

三维 BFS。

比赛时候想歪了,写成多个路径,途径多个中间点。最终 TLE。。

时间复杂度:O(mnl)

参考代码

class Solution {
    public static final int[][] DIRECTIONS = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

    public int extractMantra(String[] matrix, String mantra) {
        int m = matrix.length;
        int n = matrix[0].length();
        int l = mantra.length();

        Queue<int[]> queue = new ArrayDeque<>();
        queue.add(new int[]{0, 0, 0});
        boolean[][][] visited = new boolean[m][n][l];
        visited[0][0][0] = true;
        int step = 1;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int[] tuple = queue.remove();
                int cx = tuple[0], cy = tuple[1], cl = tuple[2];
                // 可以提取
                if (matrix[cx].charAt(cy) == mantra.charAt(cl)) {
                    if (cl == l - 1) {
                        return step;
                    }
                    if (!visited[cx][cy][cl + 1]) {
                        visited[cx][cy][cl + 1] = true;
                        queue.add(new int[]{cx, cy, cl + 1});
                    }
                }

                for (int[] dir : DIRECTIONS) {
                    int nx = cx + dir[0], ny = cy + dir[1];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                        if (!visited[nx][ny][cl]) {
                            visited[nx][ny][cl] = true;
                            queue.add(new int[]{nx, ny, cl});
                        }
                    }
                }
            }
            step++;
        }
        return -1;
    }
}

T4. 生物进化录(8 分)

解题思路

树形 DP + 排序。

相似题目: 761. 特殊的二进制序列open in new window

时间复杂度:O(n^2)

参考代码

class Solution {
    private Map<Integer, List<Integer>> g;

    public String evolutionaryRecord(int[] parents) {
        int n = parents.length;
        g = new HashMap<>();
        for (int i = 1; i < n; i++) {
            g.computeIfAbsent(parents[i], key -> new ArrayList<>()).add(i);
        }
        // 去掉第一个 0 和末尾连续的 1
        return dfs(0).substring(1).replaceAll("1+$", "");
    }

    private String dfs(int x) {
        List<String> a = new ArrayList<>();
        for (int y : g.getOrDefault(x, new ArrayList<>())) {
            a.add(dfs(y));
        }
        Collections.sort(a);
        return "0" + String.join("", a) + "1";
    }
}

T5. 与非的谜题(9 分)

解题思路

暴力出奇迹。

正解应为:拆位 + 线段树。NAND 的性质:NAND 0 一定等于 1,NAND 1 相当于取反。

时间复杂度:O(能过)

参考代码

class Solution {
    private static int FULL;

    public int getNandResult(int k, int[] arr, int[][] operations) {
        FULL = (1 << k) - 1;
        int res = 0;
        for (int[] op : operations) {
            int type = op[0], x = op[1], y = op[2];
            if (type == 1) {
                for (int ai : arr) y = nand(y, ai);
                // 偶数次
                if ((x & 1) == 0) {
                    for (int ai : arr) y = nand(y, ai);
                }
                res ^= y;
            } else {
                arr[x] = y;
            }
        }
        return res;
    }

    private int nand(int a, int b) {
        return ~(a & b) & FULL;
    }
}

T6. 万灵之树(10 分)

解题思路

宫万题解 https://leetcode.cn/problems/cnHoX6/solution/gong-mo-ti-jie-by-hqztrue-q5a0/

  1. 魔鬼数字 1996090921 = 11 * 17 * 17 * 293 * 2143;初步看是蛙佬手写 hash 时的专属数字;
  2. Function/BiFunction 是 Java 的函数编程接口;
  3. 扩展欧几里得法 (exgcd) 求 逆元 (inv)
  4. 阶乘函数 (fac)
  5. 组合函数 (C)
  6. ++T; 的作用就是清空 哈希表(妙啊);

时间复杂度:O(能过)

参考代码

class Solution {
    private static final int N = 9, M0 = 205;
    private final int[] pow10 = new int[M0], pinv = new int[M0], l = new int[N], len = new int[1 << N];
    private int n, ans, p, r, B;
    private final Map<Integer, List<Integer>> c = new HashMap<>();
    private final Map<Integer, List<Node>> d = new HashMap<>();
    private final Map<Integer, Integer> hash = new HashMap<>();

    public int treeOfInfiniteSouls(int[] gem, int p, int target) {
        n = gem.length;
        ans = 0;
        B = (n + 2) / 3;
        this.p = p;
        this.r = target;

        if (p == 2 || p == 5) {
            return p == 2 && target == 1 || p == 5 && target == 4 ? (int) (C((n - 1) * 2, n - 1) / n * fac(n)) : 0;
        }
        pow10[0] = 1 % p;
        for (int i = 1; i < M0; ++i) {
            pow10[i] = (int) ((long) pow10[i - 1] * 10 % p);
        }
        for (int i = 0; i < M0; ++i) {
            pinv[i] = (int) inv(pow10[i], p);
        }
        for (int i = 0; i < n; ++i) {
            l[i] = log10(gem[i]);
        }
        for (int i = 1; i < (1 << n); ++i) {
            len[i] = (pop(i) * 2 - 1) * 2;
            for (int j = 0; j < n; ++j) {
                if ((i & (1 << j)) > 0) {
                    len[i] += l[j];
                }
            }
        }
        for (int i = 0; i < n; ++i) {
            c.computeIfAbsent(1 << i, key -> new ArrayList<>()).add((int) (((long) gem[i] * 10 + pow10[l[i] + 1] + 9) % p));
        }
        for (int i = 1; i < (1 << n); ++i) {
            if (pop(i) <= B * 2) {  //component below u
                int t = pow10[len[i] - 1] + 9;
                for (int j = (i - 1) & i; j != 0; j = (j - 1) & i) {
                    if (n == 9 || pop(i) < Math.max((n + 1) / 2, 2)
                            || Math.max(pop(j), pop(i - j)) <= Math.min(B, (n - 1) / 2)) {
                        for (Integer v1 : c.getOrDefault(j, new ArrayList<>())) {
                            long t1 = (long) v1 * pow10[len[i - j] + 1] + t;
                            for (Integer v2 : c.getOrDefault(i - j, new ArrayList<>())) {
                                c.computeIfAbsent(i, key -> new ArrayList<>()).add((int) (((long) v2 * 10 + t1) % p));
                            }
                        }
                    }
                }
            }
        }
        d.computeIfAbsent(1 << n, key -> new ArrayList<>()).add(new Node(0, 0, 0)); //component above u
        BiFunction<Integer, Integer, Boolean> yes = (x, y) -> true;
        BiFunction<Integer, Integer, Boolean> no = (x, y) -> false;
        if (n == 9) {
            calc(4, yes, no, (j) -> pop(j) != 6); //remove size 6
            calc(5, (i, j) -> j != (1 << n) || pop(i - j) >= 2, //remove size 5
                    no, (j) -> pop(j) != 5);
            calc(6, (i, j) -> j != (1 << n) || pop(i - j) == 3, //remove size 4
                    (i, j) -> j == (1 << n) && pop(i - j) == 4,
                    (j) -> pop(j) != 4);
        } else {
            calc(n / 2 + 1, yes,
                    (i, j) -> n % 2 == 0 && pop(j) == 1 && pop(i - j) == n / 2,
                    (j) -> pop(j) < (n + 1) / 2 || pop(j) > B * 2);
        }
        return ans;
    }

    private void calc(int t0, BiFunction<Integer, Integer, Boolean> f1,
                      BiFunction<Integer, Integer, Boolean> f2, Function<Integer, Boolean> f3) {
        for (int i = (1 << n) + 1; i < (1 << (n + 1)); ++i) {
            d.getOrDefault(i, new ArrayList<>()).clear();
        }
        for (int i = 1 << n; i < (1 << (n + 1)); ++i) {
            if (pop(i) <= t0) {
                boolean _f2;
                for (int j = (i - 1) & i; (j >> n) > 0; j = (j - 1) & i) {
                    if ((_f2 = f2.apply(i, j)) || f1.apply(i, j)) {
                        for (Node t : d.getOrDefault(j, new ArrayList<>())) {
                            int l1 = len[j - (1 << n)] - t.l + 2 * (j > (1 << n) ? 1 : 0);
                            for (Integer v2 : c.getOrDefault(i - j, new ArrayList<>())) {
                                d.computeIfAbsent(i, key -> new ArrayList<>()).add(new Node((t.v1 + pow10[l1]) % p,
                                        (int) (((long) t.v2 * pow10[len[i - j] + 1] + (long) v2 * 10 + 9) % p),
                                        t.l + len[i - j] + 1));
                                if (!_f2) {
                                    d.computeIfAbsent(i, key -> new ArrayList<>()).add(new Node(
                                            (int) ((t.v1 + pow10[l1 + len[i - j]] + (long) v2 * pow10[l1]) % p),
                                            (int) (((long) t.v2 * 10 + 9) % p), t.l + 1));
                                }
                            }
                        }
                    }
                }
                int j = (1 << (n + 1)) - 1 - i;
                hash.clear();
                if (f3.apply(j)) continue;
                for (Integer v : c.getOrDefault(j, new ArrayList<>())) {
                    hash.put(v, hash.getOrDefault(v, 0) + 1);
                }
                for (Node t : d.getOrDefault(i, new ArrayList<>())) {
                    int i1 = (int) ((((long) r - t.v2 - (long) t.v1 * pow10[len[j] + t.l]) % p + p) * pinv[t.l] % p);
                    ans += hash.getOrDefault(i1, 0);
                }
            }
        }
    }

    private static class Node {
        int v1, v2, l;

        public Node(int v1, int v2, int l) {
            this.v1 = v1;
            this.v2 = v2;
            this.l = l;
        }
    }

    private int pop(int x) {
        return Integer.bitCount(x);
    }

    private int log10(int n) {
        return n < 10 ? 1 : log10(n / 10) + 1;
    }

    private long inv(long a, long b) {
        x = 0;
        y = 0;
        exgcd(a, b);
        return (x % b + b) % b;
    }

    private long x, y;

    private long exgcd(long a, long b) {
        if (b == 0) {
            x = 1;
            y = 0;
            return a;
        }
        long d = exgcd(b, a % b);
        long tmp = x;
        x = y;
        y = tmp - a / b * y;
        return d;
    }

    private long fac(int n) {
        long res = 1;
        while (n > 0) res *= n--;
        return res;
    }

    private long C(int n, int m) {
        long res = 1L;
        for (int i = 1; i <= m; i++) res = res * (n - i + 1) / i;
        return res;
    }
}

个人赛

比赛时间 2023-04-22。历史上第四次参加个人赛,终于不是月末周六了。打破两题魔咒。。

T1. 补给马车(2 分)

解题思路

模拟。

时间复杂度:O(n^2)

参考代码

class Solution {
    private int halfN;

    public int[] supplyWagon(int[] supplies) {
        halfN = supplies.length / 2;
        return dfs(supplies);
    }

    private int[] dfs(int[] cur) {
        int n = cur.length;
        if (n <= halfN) {
            return cur;
        }
        int minI = 0;
        int minSum = Integer.MAX_VALUE;
        for (int i = 0; i + 1 < n; i++) {
            if (minSum > cur[i] + cur[i + 1]) {
                minSum = cur[i] + cur[i + 1];
                minI = i;
            }
        }
        int[] next = new int[n - 1];
        for (int i = 0; i <= minI; i++) {
            next[i] = cur[i];
        }
        for (int i = minI; i < n - 1; i++) {
            next[i] += cur[i + 1];
        }
        return dfs(next);
    }
}

T2. 探险营地(4 分)

解题思路

模拟。

使用 HashSet,不同营地即为差值。

时间复杂度:O(|S|)。即所有字符串的长度之和。

参考代码

class Solution {
    public int adventureCamp(String[] expeditions) {
        int n = expeditions.length;

        Set<String> set = new HashSet<>();
        for (String s : expeditions[0].split("->")) {
            // 营地的名称长度均大于 0。
            if (s.length() > 0) {
                set.add(s);
            }
        }

        int maxI = -1;
        int maxCnt = 0;
        for (int i = 1; i < n; i++) {
            Set<String> tmp = new HashSet<>();
            for (String s : expeditions[i].split("->")) {
                if (s.length() > 0) {
                    tmp.add(s);
                }
            }
            tmp.removeAll(set);
            int cnt = tmp.size();
            set.addAll(tmp);

            if (maxCnt < cnt) {
                maxCnt = cnt;
                maxI = i;
            }
        }
        return maxI;
    }
}

T3. 最强祝福力场(6 分)

解题思路

离散化 + 二维差分。

时间复杂度:O(n^2)

参考代码

class Solution {
    public int fieldOfGreatestBlessing(int[][] forceField) {
        // 离散化 begin
        Set<Double> xSet = new HashSet<>();
        Set<Double> ySet = new HashSet<>();
        for (int[] fo : forceField) {
            double x1 = fo[0] + fo[2] / 2.0, y1 = fo[1] + fo[2] / 2.0;
            double x2 = fo[0] - fo[2] / 2.0, y2 = fo[1] - fo[2] / 2.0;
            xSet.add(x1);
            xSet.add(x2);
            ySet.add(y1);
            ySet.add(y2);
        }
        int xsz = xSet.size();
        double[] xArr = new double[xsz];
        int id = 0;
        for (double x : xSet) {
            xArr[id++] = x;
        }
        Arrays.sort(xArr);
        int ysz = ySet.size();
        double[] yArr = new double[ysz];
        id = 0;
        for (double x : ySet) {
            yArr[id++] = x;
        }
        Arrays.sort(yArr);
        // 离散化 end

        PrefixSum2d prefixSum2d = new PrefixSum2d(new int[xsz][ysz]);
        for (int[] fo : forceField) {
            double x1 = fo[0] + fo[2] / 2.0, y1 = fo[1] + fo[2] / 2.0;
            double x2 = fo[0] - fo[2] / 2.0, y2 = fo[1] - fo[2] / 2.0;

            int x1id = getId(xArr, x1), x2id = getId(xArr, x2);
            int y1id = getId(yArr, y1), y2id = getId(yArr, y2);

            prefixSum2d.rangeAdd(x2id, y2id, x1id, y1id, 1);
        }
        int max = 0;
        // 原数组
        int[][] originArr = prefixSum2d.originalArray();
        for (int i = 0; i < xsz; i++) {
            for (int j = 0; j < ysz; j++) {
                max = Math.max(max, originArr[i][j]);
            }
        }
        return max;
    }

    private int getId(double[] yArr, double x) {
        return Arrays.binarySearch(yArr, x);
    }

    private static class PrefixSum2d {
        private final int M;
        private final int N;
        private final int[][] preSum2d;
        private final int[][] diff2d;

        public PrefixSum2d(int[][] matrix) {
            this.M = matrix.length;
            this.N = matrix[0].length;
            // 预处理前缀和
            preSum2d = new int[M + 1][N + 1];
            for (int i = 1; i <= M; i++) {
                for (int j = 1; j <= N; j++) {
                    // 当前格 = 上 + 左 - 左上 + 原值
                    preSum2d[i][j] = preSum2d[i - 1][j] + preSum2d[i][j - 1] - preSum2d[i - 1][j - 1] + matrix[i - 1][j - 1];
                }
            }
            // 差分 init
            diff2d = new int[M + 1][N + 1];
        }

        // 二维前缀和:求 matrix [row1,col1] 到 [row2,col2] 的累加和
        public int sumRegion(int row1, int col1, int row2, int col2) {
            return preSum2d[row2 + 1][col2 + 1] - preSum2d[row2 + 1][col1] - preSum2d[row1][col2 + 1] + preSum2d[row1][col1];
        }

        // 二维差分:matrix [row1,col1] 到 [row2,col2] 全部增加 inc
        public void rangeAdd(int row1, int col1, int row2, int col2, int inc) {
            diff2d[row1][col1] += inc;
            diff2d[row1][col2 + 1] -= inc;
            diff2d[row2 + 1][col1] -= inc;
            diff2d[row2 + 1][col2 + 1] += inc;
        }

        // 二维差分:获取原数组
        public int[][] originalArray() {
            int[][] res = new int[M][N];
            // 0 行
            res[0][0] = diff2d[0][0];
            for (int j = 1; j < N; j++) {
                res[0][j] = res[0][j - 1] + diff2d[0][j];
            }
            // 0 列
            for (int i = 1; i < M; i++) {
                res[i][0] = res[i - 1][0] + diff2d[i][0];
            }
            // 1 行 1 列。。。
            for (int i = 1; i < M; i++) {
                for (int j = 1; j < N; j++) {
                    res[i][j] = res[i - 1][j] + res[i][j - 1] - res[i - 1][j - 1] + diff2d[i][j];
                }
            }
            return res;
        }
    }
}

T4. 传送卷轴(8 分)

解题思路

BFS + 二分答案。

时间复杂度:O(mnlog(mn))

参考代码

class Solution {
    private static final int INF = (int) 1e9;
    private static final int[][] DIRECTIONS = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
    private char[][] grid;
    private int n;
    private int[][] dist;

    public int challengeOfTheKeeper(String[] maze) {
        this.n = maze.length;
        grid = new char[n][n];
        for (int i = 0; i < n; i++) {
            grid[i] = maze[i].toCharArray();
        }

        int sx = 0, sy = 0, tx = 0, ty = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                char ch = grid[i][j];
                if (ch == 'S') {
                    sx = i;
                    sy = j;
                } else if (ch == 'T') {
                    tx = i;
                    ty = j;
                }
            }
        }

        // BFS 求终点到其余点的最短路
        dist = new int[n][n];
        for (int i = 0; i < n; i++) {
            Arrays.fill(dist[i], INF);
        }
        Queue<int[]> queue = new ArrayDeque<>();
        queue.add(new int[]{tx, ty, 0});
        boolean[][] visited = new boolean[n][n];
        visited[tx][ty] = true;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int[] tuple = queue.remove();
                int cx = tuple[0], cy = tuple[1], cstep = tuple[2];
                dist[cx][cy] = cstep;

                for (int[] dir : DIRECTIONS) {
                    int nx = cx + dir[0];
                    int ny = cy + dir[1];
                    if (nx >= 0 && nx < n && ny >= 0 && ny < n && !visited[nx][ny] && grid[nx][ny] != '#') {
                        visited[nx][ny] = true;
                        queue.add(new int[]{nx, ny, cstep + 1});
                    }
                }
            }
        }

        // 无法从 S 到 T
        if (dist[sx][sy] == INF) {
            return -1;
        }

        // 二分答案
        // 走到一个位置(陷阱),传送之后还需要走 > mid 步
        // 如果在不走 # 不走陷阱的情况下,可以到达终点 => 答案 <= mid
        // 如果无法到达终点 => 答案 > mid
        int left = 0;
        int right = n * n;
        while (left < right) {
            int mid = left + (right - left) / 2;
            // 边界二分 F, F,..., F, [T, T,..., T]
            // ----------------------^
            if (checkMid(sx, sy, mid)) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left == n * n ? -1 : left;
    }

    private boolean checkMid(int sx, int sy, int mid) {
        boolean[][] vis = new boolean[n][n];
        return dfs(vis, sx, sy, mid);
    }

    private boolean dfs(boolean[][] vis, int i, int j, int mid) {
        if (i >= 0 && i < n && j >= 0 && j < n && !vis[i][j] && grid[i][j] != '#') {
            if (grid[i][j] == 'T') {
                return true;
            }
            vis[i][j] = true;
            if (grid[i][j] == '.') {
                if (grid[i][n - 1 - j] != '#' && dist[i][n - 1 - j] > mid) {
                    return false;
                }
                if (grid[n - 1 - i][j] != '#' && dist[n - 1 - i][j] > mid) {
                    return false;
                }
            }
            for (int[] dir : DIRECTIONS) {
                int nx = i + dir[0];
                int ny = j + dir[1];
                if (dfs(vis, nx, ny, mid)) {
                    return true;
                }
            }
        }
        return false;
    }
}

T5. 魔法棋盘(10 分)

解题思路

自动机 + 状态压缩 + 记忆化搜索

https://leetcode.cn/problems/1ybDKD/solution/zhuang-ya-dp-bao-sou-xia-yi-ge-zhuang-ta-9dad/

  • 时间复杂度:O(n·21^m)。在全为 ? 的情况下,dfs 最多调用 5601518 次。
  • 空间复杂度:O(n·8^m)

参考代码

class SolutionLCP76 {
    private static final int[][] TRANS = {
            // (填 B 之后的状态,填 R 之后的状态)
            // 0. 空
            // 1. 一个 B
            // 2. 一个 R
            // 3. 连续多个 B
            // 4. 连续多个 R
            // 5. BR 交替,且以 B 结尾
            // 6. BR 交替,且以 R 结尾
            {1, 2},
            {3, 6},
            {5, 4},
            {3, -1},
            {-1, 4},
            {-1, 6},
            {5, -1},
    };

    private int n, m;
    private char[][] board;
    private long[][] memo;

    public long getSchemeCount(int n, int m, String[] chessboard) {
        // 1 <= n*m <= 30 意味着什么?在旋转后,保证列数 m <= 5

        // 用 3 个比特表示 7 个状态
        // 所有列的状态组合起来 pow(7, m) 个状态 => 状态压缩
        // 每一排从左到右增量去构造 ? => 留空/B/R
        this.n = n;
        this.m = m;
        this.board = strings2chars2(chessboard);

        if (n < m) {
            this.n = m;
            this.m = n;
            this.board = rotate(board);
        }

        memo = new long[this.n + 1][1 << (this.m * 3)];
        for (int i = 0; i < this.n + 1; i++) {
            Arrays.fill(memo[i], -1);
        }
        return dfs(0, 0);
    }

    private char[][] strings2chars2(String[] strs) {
        int n = strs.length;
        int m = strs[0].length();
        char[][] res = new char[n][m];
        for (int i = 0; i < n; i++) {
            res[i] = strs[i].toCharArray();
        }
        return res;
    }

    // 行列转换
    private char[][] rotate(char[][] board) {
        int n = board.length;
        int m = board[0].length;
        char[][] res = new char[m][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                res[j][i] = board[i][j];
            }
        }
        return res;
    }

    private long dfs(int r, int mask) {
        if (memo[r][mask] != -1) {
            return memo[r][mask];
        }
        if (r == n) {
            return 1;
        }
        long res = dfs(r, 0, 0, mask);
        memo[r][mask] = res;
        return res;
    }

    private long dfs(int r, int c, int rowMask, int colMask) {
        if (c == m) {
            return dfs(r + 1, colMask);
        }
        char b = board[r][c];
        if (b == 'B') {
            return nxt(r, c, rowMask, colMask, 0);
        } else if (b == 'R') {
            return nxt(r, c, rowMask, colMask, 1);
        } else if (b == '.') {
            return dfs(r, c + 1, rowMask, colMask);
        } else {
            return nxt(r, c, rowMask, colMask, 0)
                    + nxt(r, c, rowMask, colMask, 1)
                    + dfs(r, c + 1, rowMask, colMask);
        }
    }

    // (r,c) 填充颜色 color,判断是否合法
    // 如果合法,就生成新的 rowMask 和 colMask
    private long nxt(int r, int c, int rowMask, int colMask, int color) {
        // 新的 rowMask
        int rm = TRANS[rowMask][color];
        if (rm < 0) {
            return 0;
        }
        int c3 = c * 3;
        // 新的 colMask 的第 c 列
        int cm = TRANS[(colMask >> c3) & 7][color];
        if (cm < 0) {
            return 0;
        }
        // 修改 colMask 的第 c 列
        cm = colMask & ~(7 << c3) | (cm << c3);
        return dfs(r, c + 1, rm, cm);
    }
}

(全文完)