题解-[Sgu233]骑士

骑士

题目在这里--骑士

在 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];

F(i,j,s)=\sum (F[i-1],k,T)

此时,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();
}

猜你喜欢

转载自blog.csdn.net/guoyangfan_/article/details/82255162
sgu
233