洛谷P1896-互不侵犯(状压dp)

    xiaoxiao2022-07-14  168

    题意:洛谷P1896

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

    分析:

    首先暴搜肯定被否定,时间复杂度是指数级的,所以我们就可用上状态压缩 d p dp dp啦。

    那什么是状态压缩?在这题中按照我的理解就是将一行的所有摆放状态看成不同的 01 01 01串,然后将这些 01 01 01串看成二进制数,这样每一个摆放状态就有一个 01 串 01串 01(二进制数)与其对应。 假设 1 1 1行有 4 4 4个格子,我们在第 1 、 3 1、3 13个格子放棋子, 2 、 4 2、4 24不放棋子,然后我们定义一个 01 01 01 ′ 1 ′ '1' 1表示此处有棋子, ′ 0 ′ '0' 0表示没有棋子,那么这个 01 01 01串就是 1010 1010 1010。那么这样子做有什么好处?我的答案是操作起来十分方便和节省空间。为什么方便?因为二进制下对于这些状态运用位运算十分好操作。

    对于这题我们如何操作?我们可以先预处理第一行的所有可行状态以及可行状态 c a n [ i ] can[i] can[i]所需的棋子数 n u m [ i ] num[i] num[i],然后根据题目所给条件一直往下递推。

    转移方程: d p [ i ] [ j ] [ l ] + = d p [ i − 1 ] [ p ] [ l − n u m [ j ] ] dp[i][j][l]+=dp[i-1][p][l-num[j]] dp[i][j][l]+=dp[i1][p][lnum[j]]。 其中 i i i是当前行, j j j是当前状态, p p p是上一行的可行状态, l l l是当前状态所需的棋子数,所以 l l l l − n u m [ j ] l-num[j] lnum[j]而来。

    如何判断题目所给的 8 8 8个方向上是否有冲突?因为我们是从上往下递推,所以在考虑当前行时仅用考虑此格子的上下左右和左上右上,不需要考虑左下右下。 我们同样可以预处理一行中的所有状态,将相邻的状态全部否定,那么我们在递推的时候就不用考虑这些状态了。考虑位运算。 假设上一行状态 y y y,本行状态 x x x。 同行的判断:如果一个二进制数 x x x有相邻的 1 1 1(棋子),那么 ( x < < 1 ) & x (x<<1)\&x (x<<1)&x 必定不等于 0 0 0。 左上右上的判断:我们直接让 x x x左移或右移一位再 & \& & y y y如果不等于 0 0 0则冲突。 ( ( x < < 1 ) & y ) ((x<<1)\&y) ((x<<1)&y) ! = 0 !=0 !=0 ( ( x > > 1 ) & y ) ((x>>1)\&y) ((x>>1)&y) ! = 0 !=0 !=0。 正上方的判断: ( x & y ) (x\&y) (x&y) ! = 0 !=0 !=0 递推完后,我们加上最后一行摆放有 k k k个子的状态的方案数就好了。 知道了这些这题就很简单 了。

    #include <bits/stdc++.h> using namespace std; typedef long long ll; int n, k, maxsta; ll ans, num[1 << 10], dp[10][1 << 10][10 * 10]; bool can[1 << 10]; int main(){ cin >> n >> k; maxsta = (1 << n); for(int i = 0; i < maxsta; i++) if((i & (i << 1)) == 0) can[i] = true;//同行是否有相邻的判断 for(int i = 0, temp; i < maxsta; i++){ temp = i; if(can[temp]){//预处理可行状态所需的棋子数 while(temp){ num[i] += temp % 2 == 1; temp >>= 1 ; } } } for(int i = 0; i < maxsta; i++) if(can[i] && num[i] <= k) dp[1][i][num[i]] = 1;//预处理第一行 for(int i = 2; i <= n; i++){ for(int j = 0; j < maxsta; j++){//本行 if(can[j]){ for(int p = 0; p < maxsta; p++){//上一行 if(!can[p]) continue; if((j & p) != 0) continue; if(((j << 1) & p) != 0) continue; if(((j >> 1) & p) != 0) continue; for(int l = num[j]; l <= k; l++) dp[i][j][l] += dp[i - 1][p][l - num[j]]; } } } } for(int i = 0; i < maxsta; i++) ans += dp[n][i][k]; cout << ans << '\n'; return 0; }

    蒟蒻的第一题状压 d p dp dp,不容易啊。

    最新回复(0)