AcWing 292. 炮兵阵地

AcWing 292. 炮兵阵地

一、题目描述

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

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

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

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

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

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

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

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

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

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

数据范围

N100,M10

输入样例

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

输出样例

6

二、题目解析

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

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

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

观察到数据范围 0<N100,0<M10
故我们可以把 作为动态规划的阶段,然后对于第 i 行的摆放状态进行 状态压缩

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

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

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

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

即不能排除第 i2 层摆放的棋子可以攻击到第 i 层棋子的 不合法 情况

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

闫氏DP分析法

状态表示

  • 集合 fi,j,k: 考虑前 i 层,且第 i 层状态是 j,第 i1 层状态是 k 的方案
  • 属性 fi,j,k: 该方案能够放置棋子的最大个数

状态计算fi,j,k:
fi,j,k=max(fi1,k,pre+cntj)

其中pre是枚举的能够与 kj 合法存在于三行中的所有状态

初始状态f0,0,0

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

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

果然,直接提交原始版本,系统提示: 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 @   糖豆爸爸  阅读(160)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2020-01-04 东师理想大数据中心技术路线规划
2018-01-04 随笔
Live2D
点击右上角即可分享
微信分享提示