【SSOJ 2872: 「一本通 5.4 例 1」骑士】题解

题目

\(n×n\) 的棋盘上放 \(k\) 个国王,国王可攻击相邻的 \(8\) 个格子,求使它们无法互相攻击的方案总数。

对于全部数据,\(1≤n≤10,0≤k≤n^2\)

思路

方法一:爆搜

方法二:状压dp

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

把每行压成一个二进制!

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

那么转移呢?

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

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

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

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

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

那么统计结果呢?

同理。

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

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

Code

#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;
}

posted @ 2022-01-18 15:30  zhangtingxi  阅读(69)  评论(0编辑  收藏  举报