这种棋盘类的 DP 问题首先考虑状态压缩 DP
我们定义 f[i][j][k] 表示集合为:考虑前 i 行,目前已放置了 j 个国王,其第 i 行的状态为 k 的所有可能,属性为方案数
这里的 k 是一个二进制表示的数
由于第 i 行的状态仅受前一行影响,因此我们需要考虑的是,在第 i 行状态确定的时候,第 i−1 行的状态如何取才合法,也就是我们只需要考虑两行即可
对于任意两个状态 a, b,需要满足如下条件:
-
不能有两个相邻的 1
-
a&b==0 ,即不能有两个国王处于同一列当中
-
a∥b 需要满足第一点,因为任意两个国王在纵向上不能相邻
我们首先预处理出来所有合法的状态,放在 states 中
其次,我们预处理出来状态 a 与状态 b 不产生冲突(满足上述三点要求)的所有情况,放在 heads 中
状态转移方程很简单,我们枚举第 i 层所有合法的状态 a,如果某个状态 b 不与 a 产生冲突,则表示可以从 b 转移过来
为了方便,我们还需要额外一个数组 cnt 来存储当前状态中有多少个 1
综上,状态转移方程为:
f[i][j][a]+=f[i−1][j−cnt[b]][b], 其中a, b表示状态
最后我结果我们之间输出 f[n+1][k][0] ,表示考虑到第 n+1 行,已经放了 k 个国王,且第 n+1 行的状态为 0 ,即表示一个国王都不放
完整代码:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 12, M = 1 << 10, K = 110;
long long f[N][K][M];
int cnt[M];
vector<int> states;
vector<int> heads[M];
int n, k;
bool check(int status)
{
for(int i = 0; i < n; i++)
if(status >> i & 1 && status >> i + 1 & 1)
return false;
return true;
}
int count(int status)
{
int ans = 0;
for(int i = 0; i < n; i++)
ans += status >> i & 1;
return ans;
}
int main()
{
cin >> n >> k;
for(int i = 0; i < 1 << n; i++)
if(check(i))
{
states.push_back(i);
cnt[i] = count(i);
}
for(int i = 0; i < states.size(); i++)
for(int j = 0; j < states.size(); j++)
{
int a = states[i], b = states[j];
if((a & b) == 0 && check(a | b))
heads[i].push_back(j);
}
f[0][0][0] = 1;
for(int i = 1; i <= n + 1; i++)
for(int j = 0; j <= k; j++)//棋盘当中一个都不放也需要遍历
for(int a = 0; a < states.size(); a++)//这里的a,b均不是状态
for(int b : heads[a])
if(j >= cnt[states[b]])
f[i][j][a] += f[i - 1][j - cnt[states[b]]][b];
cout << f[n + 1][k][0] << endl;
return 0;
}