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\) 在 当前行 \(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;
}