0
点赞
收藏
分享

微信扫一扫

基于连通性状态压缩的DP问题——状态压缩DP

晚熟的猫 2022-05-05 阅读 202

🎁题目: 炮兵阵地

思路: 

人话翻译一下就是给定一个N*M的矩阵,标有的位置不能放置大炮,一个旗子的上下左右四个方向,两格以内不能放置棋子。现在求可以放置棋子的最大个数。

数据范围是 N <= 100,M <=10,M是列数,N是行号。故可以考虑二进制压缩状态,把第i行的摆放状态进行状态压缩——“线性状压DP”

但是根据本题题意,第i行要考虑第i-1行和第i-2行的状态,如果定义状态的时候,只压缩当前层的状态进行转移,是不能保证本次转移是合法的。我们还需要知道前一行的状态,并根据前一行的状态和本行的状态来确定第i-2行的状态。

状态定义:

f(i,j,k)——表示当前在第i行,且第i - 1行的状态是 j ,第 i 行的状态是 k 的最大摆放数量。

那么前一个状态就表示为 f (i - 1,第 i - 2行的状态,j),可以看出,我们根据第 i - 2行的状态来转移。

f( i, j, k ) = max( f(i ,j ,k ), f( i - 1, 第 i - 2行的状态, j ) + 本行摆放大炮的个数 );

首先需要预处理一下所有的合法状态(仅考虑本行)

需要满足 (我们设k是本次枚举的状态)—— k && k >> i  & 1 ||  k >> i + 1 & 1

即,本行两个格子范围内只能出现一个大炮。

 注:用二进制数来表示所有状态,假设M = 10,即列号等于10,我们就可以用一个 [1,1 << 10)

中的所有数来表示所有状态,每一个数对应唯一的一个01串。如:1010100011(是1的地方就放

了大炮)

bool check(int k)
{
    for(int i = 0;i <  m;i ++ )
    if( (k >> i & 1) && (k >> i + 1 & 1) | (k >> i + 2 & 1) )
    return false;

    return true;

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

合法转移:

1:需要满足第 i 行的第 j 个位置如果是 1 ,那么在 第i - 2行 和 i - 1行 都要是 0;

即:(a & b ) | (a & c) | (b & c) == 0

2:需要 所有 大炮不摆放在 H 上,设b为第 i 行的状态,c为第 I - 1行的状态

即:(g[i] & c) | (g[i - 1] & b) == 0

注:(g[i]中预存了第 i 行 H的位置)

   for(int i = 1;i <= n;i ++ )
        for(int j = m - 1; j >= 0; j -- )//其实反了也没关系
        {
            char c;
            cin >> c;
            if(c == 'H')//表示是山地,不能部署大炮
            {
                g[i] += 1 << j;
            }
        }

    for(int i = 1;i <= n + 2 ;i ++ )
        for(int j = 0;j < states.size(); j ++ )
            for(int k = 0;k < states.size(); k ++ )
                for(int u = 0;u < states.size(); u ++)
                {
                    int a = states[j],b = states[k],c = states[u];
                    if((a & b) | (a & c) | (b & c))continue;
                    if(g[i] & c | g[i - 1] & b )continue;
                    f[i & 1][k][u] = max(f[i & 1][k][u],f[i - 1 & 1][j][k] + cnt[c]);
                }

 滚动数组优化:

由于本题的M = 1<<10,已经很大了,如果不用滚动数组优化的话会内存超标。

只需要在转移的时候把第一维的 参数 & 1,就可以滚起来了。这样的话第一维只用开两个

注:奇数 & 1 == 1,偶数 & 1 == 0,不是奇数就是偶数

 

代码详解:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

const int N = 110,M = 12,K = 1 << 10;
int f[2][K][K];
int g[N];
int n,m;
int cnt[K];
vector<int> states;


bool check(int k)
{
    for(int i = 0;i <  m;i ++ )
    if( (k >> i & 1) && (k >> i + 1 & 1) | (k >> i + 2 & 1) )
    return false;

    return true;

}

int count(int k)
{
    int res = 0;
    for(int i = 0;i < m;i ++)
    res += k >> i & 1;
    return res;
}




int main()
{
    cin >> n >> m;
    for(int i = 1;i <= n;i ++ )
        for(int j = m - 1; j >= 0; j -- )//其实反了也没关系
        {
            char c;
            cin >> c;
            if(c == 'H')//表示是山地,不能部署大炮
            {
                g[i] += 1 << j;
            }
        }

    //预处理所有的合法状态(只考虑本行的情况下)
    for(int i = 0;i < 1 << m;i ++ )
    if(check(i))
    {
        states.push_back(i);
        cnt[i] = count(i);
    }


    for(int i = 1;i <= n + 2 ;i ++ )
        for(int j = 0;j < states.size(); j ++ )
            for(int k = 0;k < states.size(); k ++ )
                for(int u = 0;u < states.size(); u ++)
                {
                    int a = states[j],b = states[k],c = states[u];
                    if((a & b) | (a & c) | (b & c))continue;
                    if(g[i] & c | g[i - 1] & b )continue;
                    f[i & 1][k][u] = max(f[i & 1][k][u],f[i - 1 & 1][j][k] + cnt[c]);
                }



    /*int res = 0;
    for(int i = 0;i < states.size(); i ++)
        for(int j = 0;j < states.size(); j ++ )
            res = max(res,f[n & 1][i][j]);
    cout << res;
    */

    cout << f[n + 2 & 1][0][0];

    return 0;
}

最后输出的时候也有一个小技巧,可以直接输出f [ n + 2 ][0][0]的值即是要求的答案,就相当于第n + 2行且 第 n + 2 行放了 0 个大炮,第 n - 1 行放了 0 个大炮,那么此状态就囊括了所有第 n 行的状态。

我是一只无情的AC机器~ 

举报

相关推荐

0 条评论