【训练题18:状压DP】炮兵阵地 | 洛谷 P2704 | NOI2001

炮兵阵地 | 洛谷 P2704 | NOI2001

难度

提 高 + / 省 选 − \color{blue}提高+/省选- +/
个人认为是道状压好题

题意

在这里插入图片描述
给一个二维矩阵。你可以在 P P P 的地方放炮兵,其攻击范围为黑色十字
问你:最多放多少个炮兵,他们相互之间不会攻击到?

数据范围

N ≤ 100 M ≤ 10 N\le 100\\M\le 10 N100M10

思路

  • 我们第一时间联想到一些状压DP的题目(比如互不侵犯
  • 但是这题的十字范围更大,且一行的摆放状态会对两行都产生影响
  • 我们会设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示第 i i i 行放置状态为 j j j ,且上一行放置状态为 k k k 的兵最多数量
  • 容易得到一般的转移方程 d p [ i ] [ j ] [ k ] = max ⁡ p { d p [ i − 1 ] [ k ] [ p ] + c n t j } dp[i][j][k]=\underset{p}{\max}\Big\{dp[i-1][k][p]+cnt_j\Big\} dp[i][j][k]=pmax{dp[i1][k][p]+cntj}
  • 表示这一行摆放状态为 j j j ,上一行摆放状态为 k k k ,上上行摆放转改为 p p p 的转移。
  • c n t j cnt_j cntj 表示这个状态有多少个兵。
  • 行合法性要求:每一行的兵都在 P P P 上,每一行相邻的两个兵的距离大于 2 2 2
  • 列合法性要求 j & k = 0 j\&k=0 j&k=0 j & p = 0 j\&p=0 j&p=0 k & p = 0 k\&p=0 k&p=0,并且这几行都是合法的。

但是!

  • 考虑一下,每一行最多 2 10 = 1024 2^{10}=1024 210=1024 个状态,然后行最多 100 100 100
  • 时间复杂度 O ( N ( 2 10 ) 3 ) = O ( 1 e 11 ) O(N(2^{10})^3)=O(1e11) O(N(210)3)=O(1e11)
  • 空间复杂度 O ( N ( 2 10 ) 2 ) = O ( 1 e 8 ) O(N(2^{10})^2)=O(1e8) O(N(210)2)=O(1e8)

这不是炸裂?

考虑再度压缩

  • 由于每一行的合法性判断是 相邻两个的距离大于2
  • 我们使用代码判断为 x & ( x < < 1 ) x\&(x<<1) x&(x<<1) x & ( x < < 2 ) x\&(x<<2) x&(x<<2)
  • 但是有没有想过每一行的合法性条件其实是很少的?
  • 使用代码跑过之后,易得:每一行十个元素下,最多60种可能的状态

我们使用这些状态去转移,而不是从头算这 2 10 2^{10} 210种状态有哪些合法

  • 时间复杂度 O ( N × 6 0 3 ) = O ( 2 e 7 ) O(N\times60^3)=O(2e7) O(N×603)=O(2e7)
  • 空间复杂度 O ( N × 6 0 2 ) = O ( 4 e 5 ) O(N\times60^2)=O(4e5) O(N×602)=O(4e5)

空间还能优化

  • 考虑状态转移方程,我们第一维每次需要用到的是 d p [ i ] 、 d p [ i − 1 ] 、 d p [ i − 2 ] dp[i]、dp[i-1]、dp[i-2] dp[i]dp[i1]dp[i2]
  • 这样,可以使用滚动数组优化
  • 空间复杂度 O ( 3 × 6 0 2 ) = O ( 10800 ) O(3\times60^2)=O(10800) O(3×602)=O(10800)

状压判断:每行地形合法性

  • 我们把每一行的 H H H 看做 1 1 1 P P P 看做 0 0 0
  • 这样,每一行的 山脉也被我们状态压缩成hil[x]
  • 我们只要判断 j & h i l [ x ] j\&hil[x] j&hil[x] 是否为 0 0 0 即可。

其他一些注意点

  • 答案为所有的 d p dp dp max ⁡ \max max
  • 前面两行,与其他行不同,需要单独拎出来。
  • 第二行特判的要求是你的总行数大于等于 2 2 2

补充:快速算一个二进制数字有多少位为1

  • 注意到 x&(-x) 表示 x x x 的lowbit。
  • 我们每次都减去它即可。
ll shu(int x){
    ll ans = 0;
    while(x){
        ans++;
        x -= x & (-x);
    }
    return ans;
}

核心代码

  • 时间复杂度 O ( N × 6 0 3 ) = O ( 2 e 7 ) O(N\times60^3)=O(2e7) O(N×603)=O(2e7) 有许多非法性剪枝,不会跑满
  • 空间复杂度 O ( 3 × 6 0 2 ) = O ( 10800 ) O(3\times60^2)=O(10800) O(3×602)=O(10800)

T i m e s : 330 ( m s ) M e m o r y : 772 ( K B ) Times:330(ms)\\Memory:772(KB) Times:330(ms)Memory:772(KB)

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 2e5+50;

ll dp[5][111][111];
ll cnt[1111];
ll hil[111];
string ss[111];

ll shu(int x){
    ll ans = 0;
    while(x){
        ans++;
        x -= x & (-x);
    }
    return ans;
}
int N,K,ed;
bool check(int x){
    if(x & (x << 1))return false;
    if(x & (x << 2))return false;
    return true;
}
vector<int>V;
int main()
{
    cin >> N >> K;
    for(int i = 0;i < N;++i){
        cin >> ss[i];
        int t = 0;
        for(int j = 0;j < ss[i].size();++j){
            if(ss[i][j] == 'H')t += (1 << j);
        }
        hil[i] = t;
    }
    ll ans = 0;
    for(int i = 0;i < (1 << K);++i){
        if(check(i)){
            V.push_back(i),cnt[i] = shu(i);
            if(!(i & hil[0]))dp[0][V.size() - 1][0] = cnt[i],ans = max(ans,cnt[i]);
        }
    }
    ed = V.size();
    if(N >= 2)
    for(int i = 0;i < ed;++i){
        int tx = V[i];
        if(tx & hil[0])continue;
        for(int j = 0;j < ed;++j){
            int ty = V[j];
            if(ty & hil[1])continue;
            if(tx & ty)continue;

            dp[1][j][i] = dp[0][i][0] + cnt[ty];
            ans = max(ans,dp[1][j][i]);
        }
    }

    for(int i = 2;i < N;++i){
        for(int j = 0;j < ed;++j)
        for(int k = 0;j < ed;++j)
            dp[i%3][j][k] = 0;

        for(int j = 0;j < ed;++j){
            int tx = V[j];
            if(tx & hil[i])continue;
            for(int k = 0;k < ed;++k){
                int ty = V[k];
                if(ty & hil[i-1])continue;
                if(tx & ty)continue;

                for(int p = 0;p < ed;++p){
                    int tp = V[p];
                    if(tp & hil[i-2])continue;
                    if(tp & tx)continue;
                    if(tp & ty)continue;

                    dp[i % 3][j][k] = max(dp[i % 3][j][k],dp[(i-1 +3)%3][k][p] + cnt[tx]);
                    ans = max(ans,dp[i%3][j][k]);
                }
            }
        }
    }
    cout << ans;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值