题面
题目描述
在 N × N N \times N N×N 的棋盘里面放 K K K 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 8 8 个格子。
输入格式
只有一行,包含两个数 N , K N,K N,K。
输出格式
所得的方案数
样例 #1
样例输入 #1
3 2
样例输出 #1
16
提示
数据范围及约定
对于全部数据, 1 ≤ N ≤ 9 1 \le N \le 9 1≤N≤9, 0 ≤ K ≤ N × N 0 \le K \le N\times N 0≤K≤N×N。
题解
思路&分析
这题
N
N
N和
K
K
K的范围非常小, 而且国王只有在/不在这一格两种状态, 可以考虑状压DP
我们定义一个状态
d
p
[
i
]
[
j
]
[
S
]
dp[i][j][S]
dp[i][j][S]表示当前模拟到了第
i
i
i行, 一共用了
j
j
j个王, 当前行的状态为
S
S
S(一个01串, 被压成10进制, 1表示该格放了王, 0表示没有
e
g
:
10
1
(
2
进制
)
=
=
5
(
10
进制
)
eg: \;101_{(2进制)}==5_{(10进制)}
eg:101(2进制)==5(10进制)表示1号格和3号格放了王, 2号格没有)
现在考虑状态转移
d
p
[
i
]
[
j
]
[
S
i
]
+
=
d
p
[
i
−
1
]
[
j
−
n
u
m
S
i
]
[
S
i
−
1
]
dp[i][j][S_i]+=dp[i-1][j-num_{S_i}][S_{i-1}]
dp[i][j][Si]+=dp[i−1][j−numSi][Si−1]
n
u
m
S
num_S
numS表示
S
S
S中有多少个1(多少个王)
由题意可得, 一个王周围 3 × 3 3\times3 3×3范围内不允许存在王
所以
S
S
S对于自身, 应满足
S
&
S
<
<
1
∣
S
&
S
>
>
1
=
0
(
请自行体会
)
S\;\&\;S<<1\;\:\;\;|\;\;\;\;S\;\&\;S>>1\;\;=\;\;0_{(请自行体会)}
S&S<<1∣S&S>>1=0(请自行体会)同理 亦得, 对于上下两行的
S
i
S_i
Si和
S
i
−
1
S_{i-1}
Si−1, 也应当满足
(
S
i
∣
S
i
>
>
1
∣
S
i
<
<
1
)
&
S
i
−
1
=
0
(
也请自行体会
)
(S_i\;|\;S_i>>1\;|\;S_i<<1)\;\&\;S_{i-1}\;\;=\;\;0_{(也请自行体会)}
(Si∣Si>>1∣Si<<1)&Si−1=0(也请自行体会)所以, 我们只需要枚举
i
,
j
,
S
i
i,\;j,\;S_i
i,j,Si和
S
i
−
1
S_{i-1}
Si−1即可
时间复杂度:
O
(
n
k
2
2
n
)
O(nk2^{2n})
O(nk22n)
优化
我们发现, 枚举所有
S
S
S的过程是由很多重复的, 每次都要判断其合法性
所以我们可以预处理出合法的状态
S
S
S, 把它们记录下来, 每次调用它们即可
void init(){
for(int x=0;x<(1<<n);x++){
if(!((x&(x<<1))||(x&(x>>1)))){
cnt++;
S[cnt]=x;
int xx=x,ans=0;
while(xx!=0){
if(xx%2==1){
num[x]++;
}
xx>>=1;
}
}
}
}
其中, 当
n
=
9
n=9
n=9时, 只有
341
341
341种合法状况
时间复杂度就降成了
O
(
34
1
2
n
k
)
(
m
a
x
n
=
84
,
768
,
849
≤
3
×
1
0
8
)
O(341^2nk)_{(maxn=84,768,849\le3\times10^8)}
O(3412nk)(maxn=84,768,849≤3×108)
至此就结束哩
AC Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define YES "YES"
int n,k;
int num[1<<10],S[1<<10],cnt=0;
int dp[10][82][1<<9];
void init(){
for(int x=0;x<(1<<n);x++){
if(!((x&(x<<1))||(x&(x>>1)))){
cnt++;
S[cnt]=x;
int xx=x,ans=0;
while(xx!=0){
if(xx%2==1){
num[x]++;
}
xx>>=1;
}
}
}
}
void find(int x){
int a[10];
int ans=0;
while(x!=0){
if(x&1){
a[++ans]=1;
}else{
a[++ans]=0;
}
x>>=1;
}
for(int x=10;x>ans;x--){
cout << 0;
}
for(int x=ans;x>=1;x--){
cout << a[x];
}
// cout << endl;
}
signed main(){
cin >> n >> k;
if(k>25){
cout << 0;
return 0;
}
init();
// for(int x=1;x<=cnt;x++){
// cout << x << ':' << num[S[x]] << ' ' << S[x] << ' ';
// find(S[x]);
// }
dp[0][0][0]=1;
for(int x=1;x<=n;x++){
for(int y=0;y<=k;y++){
for(int z=1;z<=cnt;z++){
int now1=S[z];
int ans1=num[now1];
if(ans1>y){
continue;
}
for(int i=1;i<=cnt;i++){
int now2=S[i];
int ans2=num[now2];
if(!( ( now1 | ((now1<<1) | (now1>>1)) ) & (now2) )){
dp[x][y][now1]+=dp[x-1][y-ans1][now2];
}
}
}
}
}
int ans=0;
for(int x=1;x<=cnt;x++){
ans+=dp[n][k][S[x]];
}
cout << ans;
return 0;
}