AcWing 292. 炮兵阵地

\(AcWing\) \(292\). 炮兵阵地

一、题目描述

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

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

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

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

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

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

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

输入格式
第一行包含两个由空格分割开的正整数,分别表示 \(N\)\(M\)

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

输出格式
仅一行,包含一个整数 \(K\),表示最多能摆放的炮兵部队的数量。

数据范围

\(N≤100,M≤10\)

输入样例

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

输出样例

6

二、题目解析

关于如何对状态进行 二进制压缩,在 玉米田【线性状压DP】 有详细提及

关于如何不同层不同合法状态判断,在 小国王【线性状压DP】 有详细提及

以上两点在本篇博客中不会再重复提及

观察到数据范围 \(0<N≤100,0<M≤10\)
故我们可以把 作为动态规划的阶段,然后对于第 i 行的摆放状态进行 状态压缩

这就变成了一道标准的 线性状压DP

因此在这两题里,我们只需压缩存储 当前层的状态 ,然后枚举 合法的上个阶段 的状态进行 转移 即可

但是本题不同于 小国王玉米田,这两题中棋子的 攻击距离 只有\(1\), 玉米田比小国王麻烦些的是因为它有些玉米地是不育的,需要处理。

但是本题的棋子攻击范围是 \(2\),我们只压缩当前层一层状态后进行转移,是不能保证该次转移是 合法的

即不能排除第 \(i−2\) 层摆放的棋子可以攻击到第 \(i\) 层棋子的 不合法 情况

而解决该问题的手段就是:压缩存储两层的信息,然后枚举合法的第 \(i−2\) 层状态进行转移即可

闫氏\(DP\)分析法

状态表示

  • 集合 \(f_{i,j,k}\): 考虑前 \(i\) 层,且第 \(i\) 层状态是 \(j\),第 \(i−1\) 层状态是 \(k\) 的方案
  • 属性 \(f_{i,j,k}\): 该方案能够放置棋子的最大个数

状态计算\(f_{i,j,k}\):
\(f_{i,j,k}\)=\(max(f_{i−1,k,pre}+cnt_j)\)

其中\(pre\)是枚举的能够与 \(k\)\(j\) 合法存在于三行中的所有状态

初始状态\(f_{0,0,0}\)

目标状态\(f_{n,j,k}\)(其中\(j,k\)枚举的是所有合法的相邻状态)
对于本题来说,直接开所有的状态空间,空间复杂度是 \(O(N×2^M×2^M)\) 是会爆内存的

第一维上限是行数:\(100\)
第二维的状态是\(2^M\),就是\(2^{10}=1024\)
第三维的状态是\(2^M\),就是\(2^{10}=1024\)
所以三维数组的空间上限就是\(100*1024*1024\),整数一个数位占\(8\)\(byte\),所以空间就是\(8*100*1024*1024/1024/1024=800MB\),而本题要求最大空间上限\(64MB\),所以开这么大的数组会\(MLE\),需要在空间上进行优化。我们联想到其实第\(i\)层的情况只与第\(i-1\)层相关,所以可以使用滚动数组来优化。

果然,直接提交原始版本,系统提示: Memory Limit Exceeded

然后也可以预处理出来 相邻行之间的合法转移,也能避免掉一些不必要的不合法状态的枚举

三、朴素版本(\(MLE\)

#include <bits/stdc++.h>

using namespace std;
const int N = 110;
const int M = 1 << 10;
int n, m; // n行m列,n<=100,m<=10,注意:状态压缩DP对列的长度很敏感,太大不行
int g[N]; // 记录每一列的山地情况,是一个二进制标识转化后的十进制数,最大就是全都是1,也就是2^m-1
int cnt[M];
int f[N][M][M];
vector<int> st;
vector<int> head[M];

// 检查一个状态是不是合法状态[同一行必须隔两个及两个以上才是安全的]
bool check(int x) {
    return !(x & x >> 1 || x & x >> 2);
}

// 统计某个状态中数字1的数量
int count(int s) {
    int res = 0;
    for (int i = 0; i < m; i++) // 遍历每一列
        if (s >> i & 1) res++;  // 如果此个位置数字是1,那么总的数量++
    return res;
}

int main() {
    // 输入
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++)       // n行
        for (int j = 1; j <= m; j++) { // m列
            char c;
            scanf(" %c", &c);                   // 注意scanf输入char的时候,前面要加一个空格
            if (c == 'H') g[i] += 1 << (j - 1); // 山地上不能够部署炮兵部队
        }
    // 1、预处理所有合法状态[单行合法即可]
    for (int i = 0; i < 1 << m; i++)
        if (check(i)) st.push_back(i), cnt[i] = count(i); // 预算出每个状态i里有多少个数位是1

    // 2、找出所有合法状态的合法转移[行间状态兼容检测]
    for (int a : st)
        for (int b : st)
            if (!(a & b)) // 上下行间不能存在同列的1
                head[a].push_back(b);
    // 3、dp
    for (int i = 1; i <= n; i++)      // 枚举每一行
        for (int a : st) {            // 枚举i行的每个状态
            if (g[i] & a) continue;   // 按i这样摆,确实是本行内不冲突,但如果准备放大炮的位置是高地,是不行的。
            for (int b : head[a])     // 枚举第i-1行的每个可能状态,b肯定与a不冲突
                for (int c : head[b]) // 枚举第i-2行的每个可能状态,c肯定是与b不冲突的
                    if (!(a & c))     // 如果c也与a不冲突的话,才是三行间完全兼容的方案
                        f[i][a][b] = max(f[i][a][b], f[i - 1][b][c] + cnt[a]);
        }

    // 4、枚举最终的状态最大值
    int res = 0;
    for (int a : st)
        for (int b : head[a])
            res = max(res, f[n][a][b]);

    // 5、输出
    printf("%d\n", res);
    return 0;
}

四、滚动数组优化版本

#include <bits/stdc++.h>

using namespace std;
const int N = 110;
const int M = 1 << 10;
int n, m;
int g[N];
int cnt[M];
int f[2][M][M];
vector<int> st;
vector<int> head[M];

bool check(int x) {
    return !(x & x >> 1 || x & x >> 2);
}

int count(int x) {
    int res = 0;
    while (x) {
        x = x & (x - 1);
        res++;
    }
    return res;
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            char c;
            scanf(" %c", &c);
            if (c == 'H') g[i] += 1 << (j - 1);
        }

    for (int i = 0; i < 1 << m; i++)
        if (check(i)) st.push_back(i), cnt[i] = count(i);

    for (int a : st)
        for (int b : st)
            if (!(a & b))
                head[a].push_back(b);

    for (int i = 1; i <= n; i++)
        for (int a : st) {
            if (g[i] & a) continue;
            for (int b : head[a])
                for (int c : head[b])
                    if (!(a & c))
                        f[i & 1][a][b] = max(f[i & 1][a][b], f[i - 1 & 1][b][c] + cnt[a]);
        }

    int res = 0;
    for (int a : st)
        for (int b : head[a])
            res = max(res, f[n & 1][a][b]);

    printf("%d\n", res);
    return 0;
}
posted @ 2022-01-04 14:26  糖豆爸爸  阅读(146)  评论(0编辑  收藏  举报
Live2D