洛谷 P2704 [NOI2001] 炮兵阵地

思路:状态压缩DP

状态压缩的意思,其实就是对于一个东西进行二进制压缩之后,我们再在二进制的基础上进行操作。

本题就是把地图的字符变成了二进制数。

从题目中我们知道了炮兵只能按在P这里,也就是说,P是符合题意的,我们就令它为1,它的字符就让他为0,我们把每一行字符都化为二进制的形式,二进制的范围就是对于当前的列有多少进行判断就行了,也就是这些二进制有1<<m个。m是列数。

然后,我们把每一行都化为二进制的形式之后,再来我们需要判断合法不合法,因为题目中已经说到了需要保证每个炮台都不伤害彼此,而且炮塔的伤害范围就是两个格子,上下左右都是。也就是说,我们在这个限制条件中进行安排。那么我们就需要知道1的个数有多少了,所以需要数组来存储,并且需要判断每一行乃至之后判断每行之间的位置是否合法的问题了。

计数1很简单,这里怎么处理都行,作者用了位运算的方法处理了。至于对于合法状态的判断,我们明确了,在二进制中有11或者101子串的都不行,因为两个炮台挨得太近了,这样就会炸到双方,所以不行,用位运算写就是i&i>>1和i&&i>>2这个两个条件都不能成立,才能说是合法状态。并且,我们需要知道合法状态个数是多少个,方便我们后面进行枚举操作。

OK,这样的话,预处理已经结束了,定义dp数组是重头戏,我们dp[i][j][k]含义为:在前i行中,在前后状态是j和k时符合条件的最大炮台数。那么,为什么不会有第三行呢?我们可以通过状态转移来实现。

假设我们有a,b,c三行,每一行就是一个状态,我们当前判断到dp[i][a][b],那么其实我们就该判断了dp[i-1][b][c]这样一个逻辑。而dp[i][a][b]其实就是max(dp[i-1][b][c]+num[a],dp[i][a][b])转移过来的。我们状态转移也推导出来了,其实就应该没有事了。但是,我们枚举之后,需要对所有行之后的所有合法状态进行取最大值,这是最后一步,但是我们也可以通过多列举两行取最后的行数来实现,因为都是滚动向下来的,最后滚到了n+2行时,已经没有状态了,所以可以用这样方法实现。

注意:这样盲目写出来,空间就会不够用,我们从状态方程中可以知道,这里的i状态只和i-1状态有关,也就是只有两个行有关,所以第一维可以就开两个空间,然后我们让空间&1,也就是取模2,这样就是滚动数组了,我们就优化了空间。

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<cmath> 
#include<vector>
#include<algorithm>
#include<stack>
#include<queue>
#include<deque>
#include <iomanip>
#include<sstream>
#include<numeric>
#include<map>
#include<limits.h>
#include<unordered_map>
#include<set>
#define int long long
#define MAX 11000
#define N 10000
#define inf 0x3f3f3f3f
#define _for(i,a,b) for(int i=a;i<(b);i++)
using namespace std;
typedef pair<int, int> PII;
int n, m,k;
int counts;
int dx[] = { 0,1,0,-1};
int dy[] = { 1,0,-1,0 };
int g[MAX];
int s[MAX];
int num[MAX];
int f[2][N][N];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
    cin >> n >> m;
    _for(i, 1, n + 1) {
        _for(j, 0, m) {
            char ch;
            cin >> ch;
            if (ch == 'P')
                g[i] += 1 << (m - j - 1);//地图初始化,转化为二进制数
        }
    }
    _for(i,0,1<<m) {//列举所有可能的状态
        if (!(i & i >> 1) && !(i & i >> 2))//判断合不合法的状态,即11或者101的形式
            s[counts++] = i;//计算合法状态的值,counts值是对于合法状态的计数
        _for(j, 0, m)
            num[i] += (i >> j & 1);//计算这个合法状态下有多少个1
    }
    _for(i, 1, n + 3) {
        _for(a, 0, counts) {
            _for(b, 0, counts) {
                _for(c, 0, counts) {//开始状态转移,判断行之间是不是有1相邻导致不能转移,另外适配地图的地形
                    if (!(s[a] & s[b]) && !(s[a] & s[c]) && !(s[b] & s[c]) && (g[i] & s[a]) == s[a] && (g[i - 1] & s[b]) == s[b]) {
                        f[i&1][a][b] = max(f[i - 1&1][b][c] + num[s[a]], f[i&1][a][b]);
                    }
                }
            }
        }
    }
    cout << f[n + 2&1][0][0] << endl;
    return 0;
}

上代码:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值