开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 30 天,点击查看活动详情
今天继续进行状态压缩DP的学习。
题目描述
在 的棋盘上放 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。
输入格式
共一行,包含两个整数 和 。
输出格式
共一行,表示方案总数,若不能够放置则输出 0。
数据范围
输入样例:
3 2
输出样例:
16
题目分析
这是一道状态压缩的问题。
首先考虑暴力做法,即按列枚举每个格子的状态,并于前一列比较,判断是否出错。
单论枚举格子状态复杂度便为 ,可知已经超时。
现在我们从动态规划的角度分析,定义 f[i][k][j] 表示前 i 列已经放置了 k 个国王,且第 i 列国王排布状态为 j 的方案数。
此处的排布状态为当前列按行数从小到大以 1/0 表示 放/不放 的二进制排布。
假设第 i 列状态为 a,第 i-1 列状态为 b,若 a&b==0 && check(a|b) 我们便认为其是一种有效的转移状态。 (check 函数在代码中解释,即为判断二进制排布中有无连续的 1)。
转移方程为 f[i][k][a] += f[i-1][k-cnt(a)][b] (cnt 函数为统计二进制排布中 1 的数目)
初始化 f[0][0][0] = 1,遍历每一个 i 的 f[i][0][0]。
由于合法状态远不到上限,复杂度远小于 。
Accept代码
#include <bits/stdc++.h>
using namespace std;
const int N = 12, M = 1 << 10, K = 110;
int n, m;
long long f[N][K][M];
vector<int> sta, head[M];
int cnt[M];
bool check(int x)
{
for (int i = 0; i < n; i ++)
if (1 << i & x && 1 << i + 1 & x)
return false;
return true;
}
int count(int x)
{
int ans = 0;
for (int i = 0; i < n; i ++)
if (1 << i & x) ans ++;
return ans;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < 1 << n; i ++)
if (check(i))
{
cnt[i] = count(i);
sta.push_back(i);
}
for (int i = 0; i < sta.size(); i ++)
for (int j = 0; j < sta.size(); j ++)
{
int a = sta[i], b = sta[j];
if (!(a & b) && check(a | b))
head[a].push_back(b);
}
f[0][0][0] = 1;
for (int i = 1; i <= n + 1; i ++)
for (int k = 0; k <= m; k ++)
for (int j = 0; j < sta.size(); j ++)
for (auto b : head[sta[j]])
{
int a = sta[j];
if (k >= cnt[a]) f[i][k][a] += f[i - 1][k - cnt[a]][b];
}
cout << f[n + 1][m][0];
return 0;
}