P1896 [SCOI2005]互不侵犯

在本题中, 我使用 \(m\) 来代替题目原文中的 \(k\) , 需要注意.

方法

看到 \(1 \leq n \leq 9\) 和每个格子都只有两个状态(放或不放)第一时间想到状压dp.

案例

由于样例的方案数有点多, 所以考虑一个更简单的情况:
\(3 \times 3\) 格子中放 \(3\) 个国王有多少种情况?
答案如下( \(0\) 表示不放, \(1\) 表示放).

\[\begin{gathered} \begin{pmatrix} 1 & 0 & 1 \\ 0 & 0 & 0 \\ 1 & 0 & 0 \end{pmatrix} \quad \begin{pmatrix} 1 & 0 & 1 \\ 0 & 0 & 0 \\ 0 & 1 & 0 \end{pmatrix} \quad \begin{pmatrix} 1 & 0 & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 1 \end{pmatrix} \quad \begin{pmatrix} 1 & 0 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix} \quad \begin{pmatrix} 1 & 0 & 0 \\ 0 & 0 & 0 \\ 1 & 0 & 1 \end{pmatrix} \quad \begin{pmatrix} 0 & 1 & 0 \\ 0 & 0 & 0 \\ 1 & 0 & 1 \end{pmatrix} \quad \begin{pmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \end{pmatrix} \quad \begin{pmatrix} 0 & 0 & 1 \\ 0 & 0 & 0 \\ 1 & 0 & 1 \end{pmatrix} \quad \end{gathered} \]

\(8\) 种方法.

状态

我们按行来进行转移, 所以第一维应当是行, 因为是压位dp, 所以第二维必不可少的是这一行的状态, 由于题目限制了必须放满 \(m\) 个国王, 第三维应为已经放置的国王数目.

转移

那么怎么转移呢? 我们来梳理一下限制条件:

  1. 当前行不能有相邻的 \(1\)
  2. 上一行不能有 \(1\) 在 当前行 \(1\) 的正上方或左上方或右上方.

接下来就很好做了, 找到一个上一行状态 \(t\) 和当前行状态 \(k\) 满足约束时有:
\(dp[i][j][k] = \sum {dp[i - 1][j - bit(k)][t]}\) 其中 \(bit(k)\) 表示 \(k\) 中二进制 \(1\) 的个数(即当前行有多少个国王).

技巧

在看代码之前, 有几个技巧需要掌握(明白一些位运算和二进制的应该不难理解):
具体可以看这一篇文章: 位运算技巧
知道了这些, 理解起代码就会轻松些了.

代码

#include <bits/stdc++.h>
#define int long long//这题数据会爆int, 所以要开long long
using namespace std;

inline int read() {//快读
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

int n, m, k, res, bit[1000], dp[20][100][1000];

inline bool check(int x, int y) { return x & y || x & (y << 1) || x & (y >> 1); }
//检查这两行是否能放一起, x & y表示不能在正上方, x & (y << 1) 表示不能在左上方, x & (y >> 1) 表示不能再右上方
inline bool check(int x) { return x & (x << 1); }
//检查是否有相邻的1

signed main() {
    n = read(); m = read();

    for(int i = 0; i < (1 << n); ++i)
        for(int j = 0; (1 << j) <= i; ++j)
            if(i & (1 << j)) ++bit[i];//计算i二进制中所含有1的个数

    for(int k = 0; k < (1 << n); ++k)
        if(!check(k)) dp[1][bit[k]][k] = 1;//如果没有相邻的1, 则认为它作为第一行是合法的

    for(int i = 2; i <= n ;++i) {
        for(int j = 0; j <= m; ++j) {
            for(int k = 0; k < (1 << n); ++k) {
                if(check(k) || j < bit[k]) continue;//不能有相邻的1, 并且总共所有的国王数>当前这一行的国王数(显然, 否则j - bit[k] < 0)
                for(int t = 0; t < (1 << n); ++t) {//枚举上一行的所有状态
                    if(check(k, t)) continue;//检查t能否作为k的上一行
                    dp[i][j][k] += dp[i - 1][j - bit[k]][t];//转移
                }
            }
        }
    }

    for(int k = 0; k < (1 << n); ++k) res += dp[n][m][k];//由于题目要求放满m个, 所以应当累加dp[n][m][k].
    printf("%lld\n", res);//要使用lld输出
    return 0;
}
posted @ 2022-05-09 21:36  聂天泽  阅读(23)  评论(0编辑  收藏  举报