【ACWing】292. 炮兵阵地

题目地址:

https://www.acwing.com/problem/content/294/

司令部的将军们打算在 N ∗ M N*M NM的网格地图上部署他们的炮兵部队。一个 N ∗ M N*M NM的地图由 N N N M M M列组成,地图的每一格可能是山地(用'H'表示),也可能是平原(用'P'表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围是其四个方向 2 2 2格及以内。如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入格式:
第一行包含两个由空格分割开的正整数,分别表示 N N N M M M;接下来的 N N N行,每一行含有连续的 M M M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。

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

数据范围:
N ≤ 100 , M ≤ 10 N≤100,M≤10 N100,M10

思路是动态规划,可以用状态压缩存每行的炮兵放的状况,即用一个整数的二进制位来存, 1 1 1表示有炮兵, 0 0 0表示没炮兵。设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]为只摆前 i i i行,第 i − 1 i-1 i1行的状态是 j j j,第 i i i行的状态是 k k k的情况下,最多能放多少个炮兵。那么可以按照第 i − 2 i-2 i2行是怎么放的来分类,则有: f [ i ] [ j ] [ k ] = max ⁡ p ∧ j { f [ i − 1 ] [ p ] [ j ] + c ( k ) } f[i][j][k]=\max_{p\land j}\{f[i-1][p][j]+c(k)\} f[i][j][k]=pjmax{f[i1][p][j]+c(k)}其中 c ( k ) c(k) c(k) k k k的二进制表示里 1 1 1的个数,并且 p p p遍历所有与 j j j无矛盾的合法状态。可以先预处理出来所有的行的合法状态,并预处理每个状态的 1 1 1的个数。空间方面可以用滚动数组优化。代码如下:

#include <iostream>
#include <vector>
using namespace std;

const int N = 11, M = 1 << 10;
int n, m;
// 存输入,g[i]的各个二进制位表示第i行的地理情况,1表示有山,0表示无山
int g[110];
// 存行的合法状态
vector<int> state;
int f[2][M][M];
int cnt[M];

// 判断st是否是行合法状态
bool check(int st) {
    for (int i = 0; i < m; i++)
        if ((st >> i & 1) && ((st >> i + 1 & 1) || (st >> i + 2 & 1)))
            return false;
    return true;
}

// 存st的二进制表示里有多少个1
int count(int st) {
    int res = 0;
    while (st) {
        st -= st & -st;
        res++;
    }

    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < m; j++) {
            char x;
            cin >> x;
            if (x == 'H') g[i] += 1 << j;
        }
    
    for (int i = 0; i < 1 << m; i++)
        if (check(i)) {
            state.push_back(i);
            cnt[i] = count(i);
        }

    for (int i = 1; i <= n; i++)
        for (int j = 0; j < state.size(); j++)
            for (int k = 0; k < state.size(); k++)
                for (int u = 0; u < state.size(); u++) {
                	// 如果在枚举第1行,那么此时前两行必须全是0才有意义
                    if (i == 1 && u != 0 && j != 0) continue;
                    
                    int a = state[j], b = state[k], c = state[u];
                    // 如果两行之间有冲突,则略过
                    if ((a & b) | (b & c) | (a & c)) continue;
                    // 炮兵放在山上的情形略过
                    if (g[i - 1] & a | g[i] & b) continue;
                    f[i & 1][j][k] = max(f[i & 1][j][k], f[i - 1 & 1][u][j] + cnt[b]);
                }

    int res = 0;
    for (int j = 0; j < state.size(); j++)
        for (int k = 0; k < state.size(); k++)
            res = max(res, f[n & 1][j][k]);

    cout << res << endl;

    return 0;
}

时间复杂度 O ( n 2 3 n ) O(n2^{3n}) O(n23n),但是实际没有这么高,因为行合法的状态数量远远小于 2 n 2^n 2n,空间 O ( n 2 2 n ) O(n2^{2n}) O(n22n)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值