骑士
题目在这里--骑士
在 n×n的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。
输入格式
只有一行,包含两个整数 n 和 k。
输出格式
每组数据一行为方案总数,若不能够放置则输出 0。
样例
样例输入 1
3 2
样例输出 1
16
样例输入 2
4 4
样例输出 2
79
数据范围与提示
对于全部数据,1≤n≤10,0≤k≤n。
很明显,如果使用朴素的DFS会被卡到以为程序死循环,在友(xie)好(e)的比赛环境下是肯定TLE的。那么,只能改进一下我们的算法。(看标签的应该知道了,这道题目是状态压缩动态规划)。什么是状态压缩?打个比方:用一种便于识别的方式记录下每个元素的状态,对多个元素的状态进行压缩存储。
很简单明了吧?好吧,我们现在来分析一下题目。
这题目给人的感觉像八皇后但看看这所谓的“皇后”竟然变成了“国王”(国际象棋玩过吧,就是那玩意)。国王的控制范围小的可怜,只有周边一圈(这也能当王?)。既然范围小了这么多,那DFS肯定不行(一个个枚举早爆炸了)。那么换成动态规划吧。
朴素动态规划?如果没有极高的智商肯定列不出来(比如说本蒟蒻),方程貌似很难列诶。那么我们改进一下思路,一行一行的枚举国王,当我们摆第i行是,这一行会受到前一行的影响,所以这一行的状态我们还是可以确定的。
来个小方程:我们设i表示第i行,j表示第i行状态a[j],S为T+a[j],表示到目前为止总共用的国王(这次也算),T表示上一次总共使用的国王,k表示第i-1行的状态a[k];
此时,k要满足a[k]与a[j]的两个状态是可以放置的。
很明显,这个方程是没有后效性的,现在,我们只有一个问题了:如何求j与k?
先来一张图:
你们肯定看懂了把?
没错,我们可以将状态转化为2进制,再用二进制运算搞定!(如果不会二进制运算,百度一下)
至于如何用二进制搞定,我在下面的代码会说的。
#include<bits/stdc++.h>
using namespace std;
long long f[11][155][155]={0},ans;
int num[155],s[155],N,K,s0;
inline void per()//处理一下各种状态
{
s0=0;
int k;//k表示当前这个状态有几个1;
for(register int i=0;i<(1<<N);++i)//注意!这里并不是枚举行!而是枚举状态!准备之后随时调用的状态!
{
if(i&(i<<1))continue;
/*为什么呢?因为这样i表示的二进制数不会有连续的1!分析点:二进制&、<<运算。
因为<<运算表示将i的二进制整体往左挪一个位置给0,也相当于*2
(不过*2不要管,因为我们要求的玩意跟*2没关系)。
那么好了,如果挪了一个位子后,&后的结果如果>1,就说明肯定有2个以上的1是相邻的*/
k=0;
for(register int j=0;j<N;++j)if(i&(1<<j))++k;
/*这个不用我说了吧?
共有N位,一个一个枚举哪里有1。(注意是(1<<j)不是(j<<1));*/
s[++s0]=i;//记录状态
num[s0]=k;//记录要放几个国王
}
}
inline void dp()
{
f[0][1][0]=1,ans=0;/*至于为什么f[0][1][0]=1...初始化,第一个f[0]表示第0行
(其实没有这行,方便后i-1),f[1]表示第一种a[j]=0的状态,第二个f[0]表示使用了0个国王
(毕竟连行都没有).
=1是因为不放国王也是一种状态对不对。。。)*/
for(register int i=1;i<=N;++i)
for(register int j=1;j<=s0;++j)
for(register int kk=0;kk<=K;++kk)
if(kk>=num[j])
//有必要提一下,kk表示当前放的国王(对,当前已经放完了!)的总数。
for(register int t=1;t<=s0;++t)
if(!(s[t]&s[j])&& !(s[t]&(s[j]<<1))&& !(s[t]&(s[j]>>1)))
//不分析了,了解就好,和前面差不多
f[i][j][kk]+=f[i-1][t][kk-num[j]];//不用我再说了吧。。。不懂的话评论区留个言吧。
for(register int i=1;i<=s0;++i)ans+=f[N][i][K];//累计和,自己分析一下为什么这样吧。。(懒癌发作的蒟蒻)
cout<<ans<<endl;//输出;
}
int main()
{
cin>>N>>K;
per();
dp();
}
下面是纯净无注释版本
#include<bits/stdc++.h>
using namespace std;
long long f[11][155][155]={0},ans;
int num[155],s[155],N,K,s0;
inline void per()
{
s0=0,ans=0;int k;
for(register int i=0;i<(1<<N);++i)
{
if(i&(i<<1))continue;
k=0;
for(register int j=0;j<N;++j)if(i&(1<<j))++k;
s[++s0]=i;
num[s0]=k;
}
}
inline void dp()
{
f[0][1][0]=1;
for(register int i=1;i<=N;++i)
for(register int j=1;j<=s0;++j)
for(register int kk=0;kk<=K;++kk)
if(kk>=num[j])
for(register int t=1;t<=s0;++t)
if(!(s[t]&s[j])&& !(s[t]&(s[j]<<1))&& !(s[t]&(s[j]>>1)))
f[i][j][kk]+=f[i-1][t][kk-num[j]];
for(register int i=1;i<=s0;++i)ans+=f[N][i][K];
cout<<ans<<endl;
}
int main()
{
cin>>N>>K;
per();
dp();
}