题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
注:数据有加强(2018/4/25)
输入格式
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式
所得的方案数
输入 #1
3 2
输出 #1
16
解题思路:
DP解法:
首先考虑每一层内部的合法状态,由于N<10 也就是说,用二进制位表示一个点的状态,那么状态数最多也就1024个。由于他可以攻击相邻的棋,也就是二进制位中不能有连续的1.
这个可以直接预处理处理出来,记录所有的合法状态数。以及这个状态数中对应的1的个数。因为题目只给了k个棋子。
然后考虑层与层之间的关系。考虑从上往下逐层考虑。那么新的一层就只有前一层可以影响到他。根据题目要求,就是两层状态做与运算等于0,左移和右移一位再与还是等于0,就是合法的可以共存的。那么新的一层就加上上一层的状态数。
最后还有一个地方需要考虑的就是已经放了的棋子的总数,因为题目要求放k个棋子。那么每个dp值都应该记录下当前对应的棋子总数。增加一层时,也就增加id【sta】个棋子。
代码看似三层循环复杂度很高,其实预处理合法状态的时候就去掉了一大半的状态,所以复杂度是完全可以接受的。
DFS解法:
第一步同DP解法,先预处理出所有的合法状态,以及1的个数,然后枚举第一层,依次增加一层,判断条件也相同,最后枚举第n层时,如果sum为k,则返回1。
DFS代码更简洁但是需要注意的是需要记忆化,不然会进行大量的重复搜索导致TLE。记忆化时,也是需要记忆三个参数,层数,状态数和棋子总和。加上简单的剪枝,就可以AC了。
AC代码(DP):
#include <bits/stdc++.h>
#define int long long
using namespace std;
int dp[10][100][1024]; // 三维分表代表当前层数,当前放棋子数,当前状态数
int legal[1024]; // 记录合法状态
int id[1024]; // 记录状态数对应的1的个数
signed main()
{
memset(legal,0,sizeof(legal));
memset(id,0,sizeof(id));
int n,m;
cin>>n>>m;
for(int i = 0 ; i < 1024 ; i ++) // 预处理出一层所有的合法状态
{
if(((i<<1)&i) == 0 && ((i>>1)&i) == 0)
legal[i] = 1;
int cnt =0;
for(int j = i ; j != 0 ; j >>= 1)
if(j&1) cnt ++;
if(cnt > m) legal[i] = 0;
else id[i] = cnt;
}
for(int i = 0 ; i < (1<<n) ; i ++)
if(legal[i])
dp[1][id[i]][i] = 1;
for(int i = 2 ; i <= n ;i ++) // 枚举层数
for(int j = 0 ; j < (1<<n) ; j ++) // 枚举本层状态
if(legal[j])
for(int sta = 0; sta < (1<<n) ;sta ++) // 枚举上一层状态
if(legal[sta] && (j&sta) == 0 && ((j<<1)&sta) == 0 && ((j>>1)&sta) == 0 )
for(int k = 0 ; k <= m-id[j] ; k ++)
dp[i][k+id[j]][j] += dp[i-1][k][sta];
int ans = 0;
for(int j = 0 ; j < (1<<n); j ++) // 计算最后方案数总和
ans += dp[n][m][j];
cout<<ans<<endl;
}
AC代码(DP):
#include <bits/stdc++.h>
#define int long long
using namespace std;
int dp[10][100][1024]; // 记忆化数组
int legal[1024]; // 合法状态
int id[1024];
int n,m;
int dfs(int level,int sta,int sum)
{
if(sum > m || level > n) return 0;
if(level == n-1 && sum == m) return 1; // 剪枝
if(dp[level][sum][sta] != -1) return dp[level][sum][sta]; // 已经搜索过的直接返回
int res = 0;
for(int i = 0 ; i < (1<<n) ; i ++) // 枚举第level+1层的状态
{
if(legal[i] && (i&sta) == 0 && ((sta<<1)&i) == 0 && ((sta>>1)&i) == 0)
res += dfs(level+1,i,sum + id[i]);
}
dp[level][sum][sta] = res;
return res;
}
signed main()
{
memset(legal,0,sizeof(legal));
memset(dp,-1,sizeof(dp));
memset(id,0,sizeof(id));
cin>>n>>m;
for(int i = 0 ; i < 1024 ; i ++) // 预处理出一层所有的合法状态
{
if(((i<<1)&i) == 0 && ((i>>1)&i) == 0)
legal[i] = 1;
int cnt = 0;
for(int j = i ; j != 0 ; j >>= 1)
if(j&1) cnt ++;
if(cnt > m) legal[i] = 0;
else id[i] = cnt;
}
int ans = 0;
for(int i = 0 ; i < (1<<n) ; i ++) // 枚举第1层状态
if(legal[i])
ans += dfs(0,i,id[i]);
cout<<ans<<endl;
}