傅芳杰

个人博客

You Deserve The Best


状态压缩

AcWing 1064. 小国王 原题链接

在 n×nn×n 的棋盘上放 kk 个国王,国王可攻击相邻的 88 个格子,求使它们无法互相攻击的方案总数。

输入格式

共一行,包含两个整数 nn 和 kk。

输出格式

共一行,表示方案总数,若不能够放置则输出00。

数据范围

1≤n≤101≤n≤10, 0≤k≤n20≤k≤n2

输入样例:

3 2

输出样例:

16

题目思路

6rGyaq.png

    private static int n;
    private static int m;
    private static int[] count;
    private static List<Integer> legalState;
    private static List<List<Integer>> legalTransfer;

    //x二进制表示中1的个数
    private static int countNumber(int x) {
        int res = 0;
        for (int i = 0; i < n; i++) {
            res += (x >> i) & 1;
        }
        return res;
    }

    //判断状态是否合法,不能有相邻的1
    private static boolean check(int state) {
        for (int i = 0; i < n; i++) {
            if (((state >> i) & 1) == 1 && ((state >> (i + 1)) & 1) == 1) {
                return false;
            }
        }
        return true;
    }

    private static void init() {
        count = new int[1 << n];
        legalState = new ArrayList<>();
        legalTransfer = new ArrayList<>();
        //找到所有合法状态
        for (int i = 0; i < 1 << n; i++) {
            if (check(i)) {
                legalState.add(i);
                count[i] = countNumber(i);
            }
        }
        for (int i = 0; i < legalState.size(); i++) {
            legalTransfer.add(new ArrayList<>());
        }
        //枚举所有合法状态,找到合法转移
        for (int i = 0; i < legalState.size(); i++) {
            for (int j = 0; j < legalState.size(); j++) {
                int a = legalState.get(i);
                int b = legalState.get(j);
                if ((a & b) == 0 && check(a | b)) {
                    //存储的下标
                    legalTransfer.get(i).add(j);
                }
            }
        }
    }

    public static void main(String[] args) {
        n = in.nextInt();
        m = in.nextInt();
        init();
        long[][][] dp = new long[n + 2][m + 1][1 << n];
        //什么也不放算一种方案
        dp[0][0][0] = 1;
        //枚举到第i+1行
        for (int i = 1; i <= n + 1; i++) {
            for (int j = 0; j <= m; j++) {
                //枚举第i行状态
                for (int a = 0; a < legalState.size(); a++) {
                    int stateA = legalState.get(a);
                    //合法的第i-1行状态
                    for (int b : legalTransfer.get(a)) {
                        int stateB = legalState.get(b);
                        int c = count[stateB];
                        //国王数量不够
                        if (j < c) continue;
                        dp[i][j][stateA] += dp[i - 1][j - c][stateB];
                    }
                }
            }
        }
        out.println(dp[n + 1][m][0]);
        out.flush();
        out.close();
    }

AcWing 327. 玉米田 原题链接

农夫约翰的土地由 M×NM×N 个小方格组成,现在他要在土地里种植玉米。

非常遗憾,部分土地是不育的,无法种植。

而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。

现在给定土地的大小,请你求出共有多少种种植方法。

土地上什么都不种也算一种方法。

输入格式

第 11 行包含两个整数 MM 和 NN。

第 2..M+12..M+1 行:每行包含 NN 个整数 00 或 11,用来描述整个土地的状况,11 表示该块土地肥沃,00 表示该块土地不育。

输出格式

输出总种植方法对 108108 取模后的值。

数据范围

1≤M,N≤121≤M,N≤12

输入样例:

2 3
1 1 1
0 1 0

输出样例:

9

题目思路

6rGDqs.png

    private static int n;
    private static int m;
    //用二进制表示地图
    private static int[] map;
    private static List<Integer> legalState;
    private static List<List<Integer>> legalTransfer;

    private static boolean check(int state) {
        for (int i = 0; i < m; i++) {
            if (((state >> i) & 1) == 1 && (state >> (i + 1) & 1) == 1) {
                return false;
            }
        }
        return true;
    }

    private static void init() {
        legalState = new ArrayList<>();
        legalTransfer = new ArrayList<>();
        for (int i = 0; i < 1 << m; i++) {
            if (check(i)) {
                legalState.add(i);
            }
        }
        for (int i = 0; i < legalState.size(); i++) legalTransfer.add(new ArrayList<>());
        for (int i = 0; i < legalState.size(); i++) {
            for (int j = 0; j < legalState.size(); j++) {
                int a = legalState.get(i);
                int b = legalState.get(j);
                if ((a & b) == 0) {
                    legalTransfer.get(i).add(j);
                }
            }
        }
    }

    public static void main(String[] args) {
        n = in.nextInt();
        m = in.nextInt();
        map = new int[n + 2];
        final int MOD = (int)1e8;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < m; j++) {
                int t = in.nextInt();
                //如果t是0代表不能种植,把他存为这一位已经占用了即为1
                map[i] += (t ^ 1) << j;
            }
        }
        init();
        int[][] dp = new int[n + 2][1 << m];
        dp[0][0] = 1;
        for (int i = 1; i <= n + 1; i++) {
            for (int a = 0; a < legalState.size(); a++) {
                int stateA = legalState.get(a);
                for (int b : legalTransfer.get(a)) {
                    int stateB = legalState.get(b);
                    //种植到了不能种植的地方
                    if ((map[i] & stateA) != 0) continue;
                    dp[i][stateA] = (dp[i][stateA] + dp[i - 1][stateB]) % MOD;
                }
            }
        }
        out.println(dp[n + 1][0]);
        out.flush();
        out.close();
    }

AcWing 292. 炮兵阵地 原题链接

司令部的将军们打算在 N×MN×M 的网格地图上部署他们的炮兵部队。

一个 N×MN×M 的地图由 NN 行 MM 列组成,地图的每一格可能是山地(用 H 表示),也可能是平原(用 P 表示),如下图。

在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

1185_1.jpg

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。

图上其它白色网格均攻击不到。

从图上可见炮兵的攻击范围不受地形的影响。

现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入格式

第一行包含两个由空格分割开的正整数,分别表示 NN 和 MM;

接下来的 NN 行,每一行含有连续的 MM 个字符(P 或者 H),中间没有空格。按顺序表示地图中每一行的数据。

输出格式

仅一行,包含一个整数 KK,表示最多能摆放的炮兵部队的数量。

数据范围

N≤100,M≤10N≤100,M≤10

输入样例:

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

输出样例:

6

题目思路

6rGsZn.png

    private static int n;
    private static int m;
    private static int[] map;
    private static int[] count;
    private static List<Integer> legalState;

    private static int countNumber(int x) {
        int res = 0;
        for (int i = 0; i < m; i++) {
            res += (x >> i) & 1;
        }
        return res;
    }

    private static boolean check(int state) {
        for (int i = 0; i < m; i++) {
            if (
                    ((state >> i) & 1) == 1 && (((state >> (i + 1)) & 1) == 1 || ((state >> (i + 2)) & 1) == 1)
            ) {
                return false;
            }
        }
        return true;
    }

    private static void init() {
        legalState = new ArrayList<>();
        count = new int[1 << m];
        for (int i = 0; i < 1 << m; i++) {
            if (check(i)) {
                legalState.add(i);
                count[i] = countNumber(i);
            }
        }
    }

    public static void main(String[] args) {
        n = in.nextInt();
        m = in.nextInt();
        map = new int[n + 3];
        //用滚动数组进行优化
        //i,i-1必然是一个奇数一个偶数
        int[][][] dp = new int[2][1 << m][1 << m];
        char[][] charArray = new char[n + 1][m];
        for (int i = 1; i <= n; i++) {
            String str = in.next();
            System.arraycopy(str.toCharArray(), 0, charArray[i], 0, m);
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < m; j++) {
                if (charArray[i][j] == 'H') {
                    map[i] += 1 << j;
                }
            }
        }
        init();
        for (int i = 1; i <= n + 2; i++) {
            for (int a = 0; a < legalState.size(); a++) {
                int stateA = legalState.get(a);
                for (int b = 0; b < legalState.size(); b++) {
                    int stateB = legalState.get(b);
                    for (int c = 0; c < legalState.size(); c++) {
                        int stateC = legalState.get(c);
                        //第i行和第i-1行有放在不能放的地方
                        if ((map[i] & stateA) != 0 || (map[i - 1] & stateB) != 0) {
                            continue;
                        }
                        //不能有邻边
                        if ((stateA & stateB) == 0 && (stateA & stateC) == 0 && (stateB & stateC) == 0) {
                            dp[i & 1][stateA][stateB] = Math.max(dp[i & 1][stateA][stateB], dp[(i - 1) & 1][stateB][stateC] + count[stateA]);
                        }
                    }
                }
            }
        }
        out.println(dp[(n + 2) & 1][0][0]);
        out.flush();
        out.close();
    }

AcWing 524. 愤怒的小鸟 原题链接

Kiana 最近沉迷于一款神奇的游戏无法自拔。   

简单来说,这款游戏是在一个平面上进行的。 

有一架弹弓位于 (0,0)(0,0) 处,每次 Kiana 可以用它向第一象限发射一只红色的小鸟, 小鸟们的飞行轨迹均为形如 y=ax2+bxy=ax2+bx 的曲线,其中 a,ba,b 是 Kiana 指定的参数,且必须满足 a<0a<0。

当小鸟落回地面(即 xx 轴)时,它就会瞬间消失。

在游戏的某个关卡里,平面的第一象限中有 nn 只绿色的小猪,其中第 ii 只小猪所在的坐标为 (xi,yi)(xi,yi)。 

如果某只小鸟的飞行轨迹经过了 (xi, yi)(xi, yi),那么第 ii 只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行; 

如果一只小鸟的飞行轨迹没有经过 (xi, yi)(xi, yi),那么这只小鸟飞行的全过程就不会对第 ii 只小猪产生任何影响。 

例如,若两只小猪分别位于 (1,3)(1,3) 和 (3,3)(3,3),Kiana 可以选择发射一只飞行轨迹为 y=−x2+4xy=−x2+4x 的小鸟,这样两只小猪就会被这只小鸟一起消灭。 

而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。 

这款神奇游戏的每个关卡对 Kiana 来说都很难,所以 Kiana 还输入了一些神秘的指令,使得自己能更轻松地完成这个这个游戏。   

这些指令将在输入格式中详述。 

假设这款游戏一共有 TT 个关卡,现在 Kiana 想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。  

由于她不会算,所以希望由你告诉她。

注意:本题除 NOIP 原数据外,还包含加强数据。

输入格式

第一行包含一个正整数 TT,表示游戏的关卡总数。

下面依次输入这 TT 个关卡的信息。

每个关卡第一行包含两个非负整数 n,mn,m,分别表示该关卡中的小猪数量和 Kiana 输入的神秘指令类型。

接下来的 nn 行中,第 ii 行包含两个正实数 (xi,yi)(xi,yi),表示第 ii 只小猪坐标为 (xi,yi)(xi,yi),数据保证同一个关卡中不存在两只坐标完全相同的小猪。

如果 m=0m=0,表示 Kiana 输入了一个没有任何作用的指令。

如果 m=1m=1,则这个关卡将会满足:至多用 ⌈n/3+1⌉⌈n/3+1⌉ 只小鸟即可消灭所有小猪。

如果 m=2m=2,则这个关卡将会满足:一定存在一种最优解,其中有一只小鸟消灭了至少 ⌊n/3⌋⌊n/3⌋ 只小猪。

保证 1≤n≤18,0≤m≤2,0<xi,yi<101≤n≤18,0≤m≤2,0<xi,yi<10,输入中的实数均保留到小数点后两位。

上文中,符号 ⌈c⌉⌈c⌉ 和 ⌊c⌋⌊c⌋ 分别表示对 cc 向上取整和向下取整,例如 :⌈2.1⌉=⌈2.9⌉=⌈3.0⌉=⌊3.0⌋=⌊3.1⌋=⌊3.9⌋=3⌈2.1⌉=⌈2.9⌉=⌈3.0⌉=⌊3.0⌋=⌊3.1⌋=⌊3.9⌋=3。

输出格式

对每个关卡依次输出一行答案。

输出的每一行包含一个正整数,表示相应的关卡中,消灭所有小猪最少需要的小鸟数量。

数据范围

QQ截图20210311115727.png

输入样例:

2
2 0
1.00 3.00
3.00 3.00
5 2
1.00 5.00
2.00 8.00
3.00 9.00
4.00 8.00
5.00 5.00

输出样例:

1
1

题目思路

6rG6I0.png

    private static int n;
    private static int m;
    private static int[][] path;
    private static Bird[] birds;
    private static final double EPS = 1e-8;
    private static int compare(double a, double b) {
        if (Math.abs(a - b) <= 1e-8) return 0;
        if (a > b) return 1;
        return -1;
    }
    private static class Bird {
        private double x;
        private double y;

        public Bird(double x, double y) {
            this.x = x;
            this.y = y;
        }
    }

    private static void initPath() {
        //存储两个点所组成的抛物线,能够覆盖的情况二进制表示
        path = new int[n][n];
        for (int i = 0; i < n; i++) {
            //这条线一定可以覆盖自己
            //能够处理只有一个点的情况
            path[i][i] = 1 << i;
            for (int j = 0; j < n; j++) {
                double x1 = birds[i].x;
                double x2 = birds[j].x;
                double y1 = birds[i].y;
                double y2 = birds[j].y;
                //x1 != x2
                if (compare(x1, x2) == 0) continue;
                double a = (y1 / x1 - y2 / x2) / (x1 - x2);
                double b = y1 / x1 - a * x1;
                // a要<0
                if (compare(a, 0D) >= 0) continue;
                int state = 0;
                //看这条抛物线能够穿过哪些点
                for (int k = 0; k < n; k++) {
                    double x = birds[k].x;
                    double y = birds[k].y;
                    if (compare(a * x * x + b * x, y) == 0) {
                        state += 1 << k;
                    }
                }
                path[i][j] = state;
            }
        }
    }

    public static void main(String[] args) {
        int t = in.nextInt();
        while (t-- > 0) {
            n = in.nextInt();
            m = in.nextInt();
            int[] dp = new int[1 << n];
            Arrays.fill(dp, Integer.MAX_VALUE / 2);
            //状态为0时,不需要抛物线
            dp[0] = 0;
            birds = new Bird[n];
            for (int i = 0; i < n; i++) birds[i] = new Bird(in.nextDouble(), in.nextDouble());
            initPath();
            //i == (1 << n) - 1时就已经全部打到了
            for (int i = 0; i < 1 << n; i++) {
                int x = -1;
                for (int j = 0; j < n; j++) {
                    //找到一个没有被打到的点
                    if (((i >> j) & 1) == 0) {
                        x = j;
                        break;
                    }
                }
                //全是1的情况
                if (x == -1) {
                    break;
                }
                //枚举所有经过x的抛物线
                for (int j = 0; j < n; j++) {
                    // (i | path[x][j])就是选择这条抛物线之后的new_state
                    // 此时状态i可以在选择这条抛物线时更新new_state的状态
                    dp[i | path[x][j]] = Math.min(dp[i | path[x][j]], dp[i] + 1);
                }
            }
            //有n只小猪没有被打
            out.println(dp[(1 << n) - 1]);
        }
        out.flush();
        out.close();
    }