题解/算法 {292. 炮兵阵地}

题解/算法 {292. 炮兵阵地}

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

经典的二维矩阵的状压DP, 即以每个行为单位, 将一整行压缩为一个2进制状态;

-{ 错误做法;

(i,j): 第i行, j为第(i,i-1)行的状态 进行完(或运算)后的结果 , 令row为第i+1行的状态, 那么 虽然通过j & row 确实可以判定 当前row是否是合法的 即是否可以与j来递推下面的状态, 但是 你无法得到 新状态到底是多少, 即(i+1, jj) 这个jj 是多少 你是得不到的, 因为你不知道第i行的状态;

-}

因此, 需要同时记录i, i-1的状态, 即(i, j, k): i行, i行的状态为j, (i-1)行状态为k ;

由于状态有1<<N = 1000, 那么这显然状态太多了, 因此 先预处理所有合法状态 (提前打表可以发现, 最多只有60个), 即(M=100, 60, 60);

注意点: 不要把DP初始化为0, 因为 这里求的 不是方案数, 而是最大放棋子的个数, 那么你设置为0, 会有歧义的;
. 比如(i,j,k) 这个状态是非法的, 但他的值为0; 而假如一个状态是合法的 他的值 可能也是0, 这就歧义了;

代码

void __Solve(){
    cin>> M>> N;
    A.resize( M);
    for( int i = 0; i < M; ++i){
        string s; cin>> s;

        int st = 0;
        for( int j = 0; j < N; ++j){
            if( s[ j] == 'H'){
                st |= (1 << j);
            }
        }
        A[ i] = st;
    }

    {
        ValidSTs.clear();
        for( int st = 0; st < (1 << N); ++st){
            if( (st & (st >> 1)) != 0) continue;
            if( (st & (st >> 2)) != 0) continue;

            ValidSTs.push_back( st);
        }
    }

    if( M == 1){
        int ANS = 0;
        for( auto st : ValidSTs){
            if( (st & A[ 0]) != 0) continue;

            MAX_SELF_( ANS, __builtin_popcount( st));
        }
        cout<< ANS<< ED_;

        return;
    }

    { // DP
        //> DP( 1, ?)
        memset( DP[ 1], -1, sizeof( DP[ 1]));
        for( int cur = 0; cur < (int)ValidSTs.size(); ++cur){
            for( int pre = 0; pre < (int)ValidSTs.size(); ++pre){
                if( (ValidSTs[ cur] & ValidSTs[ pre]) != 0) continue;
                if( (ValidSTs[ cur] & A[ 1]) != 0) continue;
                if( (ValidSTs[ pre] & A[ 0]) != 0) continue;

//> 不要写成: `__builtin_popcount( ValidSTs[ cur] & ValidSTs[ pre])`
                DP[ 1][ cur][ pre] = __builtin_popcount( ValidSTs[ cur]) + __builtin_popcount( ValidSTs[ pre]);
            }
        }

        //> DP( >1, ?)
        for( int row = 2; row < M; ++row){
            memset( DP[ row], -1, sizeof( DP[ row]));

            for( int cur = 0; cur < (int)ValidSTs.size(); ++cur){
                if( (ValidSTs[ cur] & A[ row]) != 0) continue;

                for( int pre = 0; pre < (int)ValidSTs.size(); ++pre){
                    if( (ValidSTs[ cur] & ValidSTs[ pre]) != 0) continue;

                    for( int pp = 0; pp < (int)ValidSTs.size(); ++pp){
                        if( (ValidSTs[ cur] & ValidSTs[ pp]) != 0) continue;
                        if( DP[ row - 1][ pre][ pp] == -1) continue;

                        auto & curDP = DP[ row][ cur][ pre];

                        MAX_SELF_( curDP, DP[ row - 1][ pre][ pp] + __builtin_popcount( ValidSTs[ cur]));
                    }
                }
            }
        }
    } // DP

    int ANS = 0;
    for( int cur = 0; cur < (int)ValidSTs.size(); ++cur){
        for( int pre = 0; pre < (int)ValidSTs.size(); ++pre){
            MAX_SELF_( ANS, DP[ M - 1][ cur][ pre]);
        }
    }
    cout<< ANS<< ED_;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值