看了很多题解都没看懂,直到我看到了bilibili董晓算法的视频我才理解。
这道题要用到位运算和多重背包。先说一下位运算:
!(i&i>>1)代表将i右移一维看看和原来的i有没有重叠的1,如果有,那么结果不为零,前面的感叹号取反后不为0,if语句就为假,那他有什么用呢?作用就是判断i的二进制是否有无相邻的1。
!(a&b)表示判断a和b的二进制有无相同位置的1,
!(a<<1&b)表示a的右边有没有1,如果有的话会和b的二进制的某一位重合导致结果为真,取反后结果为假,
!(a>>1&b)同理。这样我们就可以用二进制来筛选符合条件的国王的排列了。
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100
#define int long long
int N,K;
int num[maxn];
int st[maxn],top;
int f[10][100][8192];
signed main(){
cin>>N>>K;
for(int i=0;i<(1<<N);i++){
if(!(i&i>>1)){
st[top++]=i;
for(int j=0;j<N;j++){
num[i]+=(i>>j&1);
}
}
}
f[0][0][0]=1;
for(int i=1;i<=N;i++){
for(int j=0;j<=K;j++){
for(int a=0;a<top;a++){
for(int b=0;b<top;b++){
if((j-num[st[a]]>=0)&&!(st[a]&st[b])&&!(st[a]<<1&st[b])&&!(st[a]>>1&st[b])){
f[i][j][a]+=f[i-1][j-num[st[a]]][b];
}
}
}
}
}
int ans=0;
for(int i=0;i<top;i++){
ans+=f[N][K][i];
}
cout<<ans;
return 0;
}
这里 f[N][K][S]代表的是当我处于第N行并且目前已经选择了K个国王时,国王的二进制排列为S的方案数。
洛谷的题解区总是喜欢谜语人,他们的说法是这样的:f[N][K][S]代表第N行选择了K个国王状态为S的方案数,对于新手来说理解起来简直费劲。他并没有说清楚“状态为S”是什么意思。
S的状态需要预处理出来,从0枚举到2^N-1,然后先把二进制有相邻的位都是1的数筛掉,剩下的放到数组st里面top++,之后计算每个符合条件的S中有多少个1,也就是num数组,for循环j,如果0100100这里面有1,那么当i>>j&1结果就是1,num[i]++就好了。