本文搬运自本人初中博客园博客,若图片加载不出来,可到原文查看:https://www.cnblogs.com/zhangtingxi/p/15818062.html

题目

n×nn×n 的棋盘上放 kk 个国王,国王可攻击相邻的 88 个格子,求使它们无法互相攻击的方案总数。

对于全部数据,1n10,0kn21≤n≤10,0≤k≤n^2

思路

方法一:爆搜

方法二:状压dp

每行很大,不可能开个十几维数组,怎么办?

把每行压成一个二进制!

dp(i,j,x)dp(i, j, x) 表示第 ii 行放置状态为 xx,前 ii 行共放 jj 个棋子的方案数。

那么转移呢?

我们可以枚举上一层的情况 yy (也是一个二进制数)。

此时如何满足无法互相攻击呢?

  • (x&(x<<1)=0(y&(y<<1))=0:左右不互相攻击
  • (x&y)==0:上下不互相攻击
  • (x&(y<<1))==0(x&(y>>1))==0:斜着不互相攻击

好了,解决了状态和转移,初始化呢?

在第一行,我们可以枚举所有可能,赋值为1。

那么统计结果呢?

同理。

我们可以枚举最后一行的所有可能,加起来,就是答案。

时间复杂度:状态+转移= O(n322n)O(n^32^{2n})

Code

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
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, m, i, j, k;
int dp[15][150][1050];
int ans, a, b;

int qiu(int x) //求这种状态有多少个棋子
{
int ans=0;
while(x) ++ans, x-=x&-x;
return ans;
}

signed main()
{
scanf("%lld%lld", &n, &k);
for(i=0; i<(1<<n); ++i) //初始化
if((m=qiu(i))<=k && !(i&(i<<1)))
dp[1][m][i]=1;
for(i=2; i<=n; ++i) //第几行
for(b=0; b<(1<<n); ++b) //这一行状态
for(a=0; a<(1<<n); ++a) //上一行状态
if(!(a&b) && !(a&(b<<1)) && !(a&(b>>1)) && !(b&(b<<1))) //不互相攻击
for(j=(m=qiu(b))+qiu(a); j<=k; ++j)
dp[i][j][b]+=dp[i-1][j-m][a]; //转移
for(i=0; i<(1<<n); ++i) ans+=dp[n][k][i]; //统计答案
printf("%lld", ans);
return 0;
}