题目传送门:【BZOJ 1087】
题目大意:你需要在N×N的棋盘里面放K个国王使他们互不攻击。国王能攻击到它上下左右,以及左上、左下、右上、右下共八个方向上附近的各一个格子,共8个格子。求最终有多少种摆放方案。
( 1 ≤ n ≤ 9,0 ≤ k ≤ n
2
)
题目分析:
一开始以为是道大水题,于是直接写了个 dfs 跑暴力,然后……TLE
……
其实这道题观察到后面就会发现,n = 9 时,k = 9 已经有 60亿种情况,根本没法用搜索做,唯一的方式就是 DP。
考虑到棋盘上每一个格子只能为放或不放,每一行可能的状态(不考虑合法性)最多有
29=512
种,并且由当前行的状态可以推出下一行有哪些状态是合法的(可能会有很多种),因此我们可以使用状压 DP。
首先考虑在一行以内合法的状态,只有当国王之间互不相邻时才是合法的。接下来考虑两行之间。由于以国王为中心所在的九宫格内只能有一个棋子,即国王本身,所以在判断两行之间时,除了判断这个国王下面是否有棋子外,还需要判断它的两个斜侧有无棋子。最终将合法状态转移至 DP 方程中即可。
DP 的几个维度:dp[n][state][k] 分别代表第 n 行,状态为 state,使用了 k 个国王。
下面附上代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MX = 520;
int state[MX],num[MX]; //state:当前行的合法状态
long long dp[10][MX][MX]; //num:当前状态所需的国王数量
bool cond[MX][MX]; //cond:两行之间的合法状态
int maxs,tots = 0,n,K; //maxs:状态总数的最大值
long long ans = 0; //tots:合法的状态总数
void check(){ //判断两行之间的状态是否合法
for (int i = 1;i <= tots;i++){
int o = state[i];
for (int j = 1;j <= tots;j++){
int q = state[j];
if (q & o) continue; //分别判断是否为 010 或 010 或 010
if ((q << 1) & o) continue; // 010 100 001
if ((q >> 1) & o) continue; //(1为放棋子,0为不放棋子)
cond[o][q] = cond[q][o] = true;
}
}
}
void solve(){
dp[0][0][0] = 1;
for (int i = 1;i <= n;i++){
for (int j = 1;j <= tots;j++){
int o = state[j];
for (int k = 1;k <= tots;k++){
int q = state[k];
if (cond[o][q]){
for (int p = num[j]+num[k];p <= K;p++){
dp[i][q][p] += dp[i-1][o][p-num[k]]; //由上一个状态转移而来
}
}
}
}
}
}
int main(){
cin>>n>>K;
maxs = (1 << n) - 1;
for (int i = 0;i <= maxs;i++){
int t = i,cnt = 0;
bool ok = 1;
while (ok && t){
if ((t & 3) == 3){ //判断是否为 11 类型
ok = 0; //即:两个国王是否相邻
}
if (t & 1) cnt++;
t >>= 1;
}
if (ok){
state[++tots] = i;
num[tots] = cnt;
}
}
check();
solve();
for (int j = 1;j <= tots;j++)
ans += dp[n][state[j]][K]; //按照最后要求的状态输出
cout<<ans<<endl;
return 0;
}