[SCOI2005]互不侵犯(状压DP)

嗝~算是状压DP的经典题了~

#(mathcal{color{red}{Description}})

(N×N)的棋盘里面放(K)个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共(8)个格子。

#(mathcal{color{red}{Solution}})

嗯,然后今天(其实是前天)接触了一种新颖的(DP)——状压(DP)。其实就是一种借用二进制表示状态的方式,类似于“压缩”了一样。譬如说我需要马上得到一张01图上,某一行的状态,那我们就可以用二进制数来表示:$$100010_{(2)} = 34_{(10)}$$那么,对于这一行的状态,我们就可以用(34)这个数记录,而不是用数组来记录——瞬间减少了一维是不是?但其实还有更多的方便之处,比如说我们在(DP)的单次转移时,有时需要用到一整行的状态,用数组记录的话不便于转移,我们就需要用二进制来转移。

哦,还有,一般情况下状压之后都是倒着存的,因为这符合二进制的定义规则,比如我的一行的状态如此$$110101$$那么二进制下应该是$$101011$$可以类比一下,从左至右列标逐渐增大,但是二进制位从左到右权值逐渐减小。为了防止出现什么麻烦事儿,所以就倒着存了(qwq)

嗯,那么它方便的地方在哪里呢?就是我们可以像访问数组的一行一样,甚至更快速的访问,比如:$$(1 << k - 1) & s$$可以询问状态(s)的第(k)位上是(1)(0),$$(j >> k)<<k$$表示把状态(j)的二进制表示下最右边几位清零(你看数组就不能做直接清零)。

那对于这个题而言,1表示有我们用(dp_{i,j,k})表示前(i)行,第(i)行状态是二进制表示(十进制下的值)为(j),放置了(k)个国王的方案数,那么状态转移方程就是$$dp_{i,j,k} = sum dp_{i - 1, new, k - getlen(j)}$$

其中(new)表示我们所枚举的上一行的所有合法状态,(getlen(j))表示(j)状态有多少位为(1),因为我们所定义的(1)即为放了国王。

其实好像对于一部分的东西是可以预处理出来的,但那时我偷了个懒,直接算的(qwq)

#include <cstdio>
#include <iostream>
#define ll long long

using namespace std ;
ll dp[20][210][1026], ans ;
int N, K, NN, i, j, k, l, L ;

inline int _get(ll x){
    int ret = 0 ;
    while(x){
        if(x & 1) ret ++ ;
        x >>= 1 ;
    } return ret ;
}
inline bool check(ll x, ll y){
    if(x & y) return 1 ;
    if( (x << 1) & y) return 1 ;
    if( (x >> 1) & y) return 1 ;
    if( (y >> 1) & y) return 1 ;
    return 0 ;
}
int main(){
    cin >> N >> K ;
    dp[0][0][0] = 1, NN = (1 << N) - 1 ;
    for(i = 1; i <= N; i ++)
        for(j = 0; j <= K; j ++)
            for(k = 0; k <= NN; k ++){
                L = _get(k) ;
                if(L > j) continue ;
                if(k & (k >> 1)) continue ;
                for(l = 0; l <= NN; l ++){
                    if(check(k ,l)) continue ;
                    dp[i][j][k] += dp[i - 1][j - L][l] ;
                }
            }
    for(i = 0 ; i <= NN; i ++ ){
        ans += dp[N][K][i] ;
    }
    cout << ans ;
}

原文地址:https://www.cnblogs.com/pks-t/p/9360661.html