• [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 ;
    }
    
    
  • 相关阅读:
    第9章 线程编程(7)_线程同步4:信号量
    第9章 线程编程(6)_线程同步3:条件变量
    第9章 线程编程(5)_线程同步2:读写锁
    第9章 线程编程(4)_线程同步1:互斥锁
    第9章 线程编程(3)_线程清理和控制函数
    第9章 线程编程(2)_线程创建和终止
    第9章 线程编程(1)_线程概念和线程标识
    第8章 信号(6)_贯穿案例2:mini shell(3)
    第8章 信号(5)_信号集和信号屏蔽
    第8章 信号(4)_中断的系统调用和函数可重入性
  • 原文地址:https://www.cnblogs.com/pks-t/p/9360661.html
Copyright © 2020-2023  润新知