[SCOI2005] 互不侵犯
题目描述
在 N × N N \times N N×N 的棋盘里面放 K K K 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 8 8 个格子。
输入格式
只有一行,包含两个数 N , K N,K N,K。
输出格式
所得的方案数
样例 #1
样例输入 #1
3 2
样例输出 #1
16
数据范围及约定
对于全部数据, 1 ≤ N ≤ 9 1 \le N \le 9 1≤N≤9, 0 ≤ K ≤ N × N 0 \le K \le N\times N 0≤K≤N×N。
原题链接
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, k; // n:棋盘边长 ,k:国王个数
vector<ll> s; // 行合法的二进制压缩的状态(状压s)
ll dp[16][106][2006]; // dp[i][j][k],表示第i行,已选取j个国王,行状态为k
ll cnt[2006]; // 表示某个s状态有多少个1,可预处理
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (ll i = 0; i < (1 << n); i++)
{ // 枚举二进制状态,从而选取符合条件的状态加入数组s
if ((((i >> 1) | (i << 1)) & i) == 0) // 如果没有相邻(即 左、右 两种相邻关系)的国王,表示该状态在一行中是符合条件的
{
s.push_back(i);
cnt[i] = __builtin_popcountll(i); // 编译器内置函数,计算无符号整数二进制表示中1的个数
}
}
dp[0][0][0] = 1; // 第0行不放国王的状态初始化为1,用于后续dp求解
for (ll i = 1; i <= n; i++)
{ // 枚举行
for (ll k1 = 0; k1 < s.size(); k1++)
{ // 枚举当前行合法的行状态
ll s1 = s[k1];
for (ll k2 = 0; k2 < s.size(); k2++)
{ // 枚举上一行合法的行状态
ll s2 = s[k2];
if (((s2 | (s2 >> 1) | (s2 << 1)) & s1) == 0)
{ // 行与行之间不存在 左上、左下、右上、右下、上、下 六种相邻关系,说明此时行间状态是符合条件的
// 下面枚举国王数量,做好国王数量的dp
for (ll j = 0; j <= k; j++)
{ // j表示加上当前行国王数量后 总的已选取国王数量
if (j - cnt[s1] >= 0) // j-cnt[s1]表示枚举到上一行时总的已选取国王数量,只有大于等于0时才是合法的(没有办法选取负数个国王doge)
{
dp[i][j][s1] += dp[i - 1][j - cnt[s1]][s2];
}
}
}
}
}
}
ll ans = 0;
for (ll i = 0; i < s.size(); i++)
{
ans += dp[n][k][s[i]]; // 统计枚举到最后一行后选取k个国王的合法状态的总方案数量
}
cout << ans << '\n';
return 0;
}