洛谷P1896《[SCOI2005]互不侵犯》

ProjectDP - 27

最基础的状压DP

题目描述

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

注:数据有加强(2018/4/25)

输入输出格式

输入格式

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

输出格式

所得的方案数

输入输出样例

输入样例

1
3 2

输出样例

1
16

解题思路

考虑状压DP

我们设 dp[i][j][k] 表示第 i 行的状态的编号为 j ,放了 k 个国王

转移方程显然
MATHJAX-SSR-1251
其中 pct(x) 表示 x 的二进制1的个数

边界条件:
MATHJAX-SSR-1252
其中 nowStat 表示当前枚举到的合法的状态

剩下的……就没啥好说的了(

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <cstdio>
#include <cstring>

#define IMPIO() std::ios::sync_with_stdio(false);
#define FILE_IN(__file) freopen(__file, 'r', stdin);
#define FILE_OUT(__file) freopen(__file, 'w', stdout);

using std::cin;
using std::cout;
using std::endl;

const int MAXN = 9 + 2;
const int MAXK = MAXN * MAXN;

int n, k;
int stats[(1 << MAXN) - 1 + 10], popc[(1 << MAXN) - 1 + 10], cnt;
long long int dp[MAXN][(1 << MAXN) - 1 + 10][MAXK];

/*
*
* dp[i][stat][k]: line i, status stat, k kings
*
*/

int Popcount(int x) {
int ret = 0;
while (x) {
if (x & 1) ++ret;
x >>= 1;
}
return ret;
}

bool CheckFailed(int stat1, int stat2) {
if ((stat1 & stat2) != 0) return true;
if ((stat1 & (stat2 << 1)) != 0) return true;
if (((stat1 << 1) & stat2) != 0) return true;
return false;
}

int main() {
IMPIO();
cin >> n >> k;
for (int i = 0; i <= (1 << n) - 1; ++i) {
if ((i & (i << 1)) != 0) continue;
stats[++cnt] = i;
dp[1][cnt][Popcount(i)] = 1;
}

for (int i = 2; i <= n; ++i) {
for (int idj = 1; idj <= cnt; ++idj) {
for (int idk = 1; idk <= cnt; ++idk) {
if (CheckFailed(stats[idj], stats[idk])) continue;
for (int l = 0; l <= k; ++l) {
dp[i][idj][Popcount(stats[idj]) + l]
+= dp[i - 1][idk][l];
}
}
}
}

long long int ans = 0;
for (int i = 1; i <= cnt; ++i) {
ans += dp[n][i][k];
}
cout << ans << endl;
return 0;
}