【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;
}
本文来自博客园,作者:zhangtingxi,转载请注明原文链接:https://www.cnblogs.com/zhangtingxi/p/15818062.html