直接说思路:
-
先解释一下什么叫“状态”:状态也就是每一行若干个国王的摆放方案,比如棋盘为3×3,我们在第1行的第1个格放国王,第二个格不放,第三个格放,那么这一行的状态为101(即放了国王就记为1,不放国王就记为0),这种状态满足国王之间隔着至少一个空格,是合法的,所以我们称之为合法状态,这个状态转成十进制就是5;如果是第1个格和第2个格都放了1,第3个格不放,那么这一行的状态为110,两个国王挨着了,所以是不合法状态,这个状态转成十进制就是6
-
用到的关键数据结构:
- state数组:存每一行合法的状态,其中下标为这个状态的编号。比如state[2]=1,就表示第2个合法状态为1,转化成二进制就是001,也就是第一个格、第二个格都没放国王,第3个格子才摆放了1个国王
- king数组:存对应状态下国王的个数,比如state[2]=1,那么king[2]=1,即第2种合法状态下,只摆了1位国王
- ans:这个变量的值是每一行中总的合法状态的个数
-
思路讲解
- dp[i][j][l]:表示前i行一共用了l位国王,并且第i行国王的摆放状态是第j种合法状态下的方案总数
- 初始化init():初始化state数组,king数组,计算合法状态数ans
- 先解决第一行的摆放情况
- 然后从第二行开始,用一套二层循环,两层循环都是循环state数组,其中外层循环是当前行的摆放状态,内层循环当作上一行的摆放状态,通过位运算更新当前行的dp数组
- 最后遍历所有方案,输出所有的dp[n][j][k]的和(k是输入的国王总数)
- 注意,位运算一定一定要加括号,不然很容易出错,因为位运算符的优先级都很低(包括<<, >>, |, ^, &)
还有一点是,每一行有多少个合法状态,涉及到不含连续1的非负整数,本题n最大为9,最多有89种合法状态,所以state数组和king数组开到100就可以
代码如下:
#include <bits/stdc++.h>
#define maxstate 100
using namespace std;
#define ll long long
//state[]存的是每一行可行的状态
//king[]存的是对应状态中可以存放的国王个数
ll state[maxstate],king[maxstate];
ll sum;
int ans;
int n,k;
ll dp[10][100][100];
//计算x的二进制有多少个1
int countOne(int x){
int cnt=0;
while (x){
if(x%2)
cnt++;
x>>=1;
}
return cnt;
}
void init(){
int tot=(1<<n)-1;
for(int i=0;i<=tot;i++){
if(!((i<<1)&i)){//这一行是用来判断i本身有没有相邻的1
state[++ans]=i;
king[ans]=countOne(i);
}
}
}
int main(){
scanf("%d%d",&n,&k);
init();
//先处理第一行
for(int i=1;i<=ans;i++){
if(king[i]>k)
continue;
dp[1][i][king[i]]=1;
}
//然后从第二行开始处理
for(int i=2;i<=n;i++){
//j是当前行的状态
for(int j=1;j<=ans;j++){
//p是上一行的状态
for(int p=1;p<=ans;p++){
if(state[p]&state[j])
continue;
if((state[p]<<1)&state[j] )
continue;
if(state[p]&(state[j]<<1))
continue;
for(int s=0;s<=k;s++){
if(king[j]+s>k)
break;
//当前行的状态个数直接是上一行的合法状态的加和
dp[i][j][king[j]+s]+=dp[i-1][p][s];
}
}
}
}
//遍历整个dp[n][],把所有dp[n][][k]加起来,也就是用光所有国王的方案的个数加起来
for(int i=1;i<=ans;i++)
sum+=dp[n][i][k];
printf("%lld",sum);
}
参考博客:
1.https://www.luogu.com.cn/blog/user16213/solution-p1896
2.https://www.luogu.com.cn/blog/da-ju-ruo-pb/solution-p1896