(第十二届蓝桥杯第二场省赛)国际象棋(状压DP)

题目:

输入样例:

3 20 12

输出样例:

914051446

分析:

看了一眼数据范围发现这一道题的数据范围比较小,但是发现如果要是搜索的话显然还是复杂度太高,这样的数据范围一般就对应于状压DP。

我们先来看一下如何进行答案枚举,我们不妨考虑第i行能够放的马的位置排列,他取决于第i-1行和第i-2行的马的位置排列,这是比较好想的,那么问题来了,每一行最多有100个位置,难道我们要用一个100位整数去枚举吗?那显然是不现实的,我们发现最多有6行,那么我们就可以令行列交换,也就是说我们按列来枚举,这样显然是不会影响最终结果的,但是这样却使我们要枚举的状态就变为了2^6,大幅度降低了我们枚举所消耗的代价。不妨就假设数据范围是1<=N<=100,1<=M<=6,这样方便之后的叙述。假设我们第i行的状态是c,第i-1行的状态是b,第i-2行的状态是a,那么为了使得马之间不能相互攻击应该满足什么条件呢?首先是第i行和第i-1行之间,因为这两行是相邻的,所有要想使得他们之间不相互攻击,就必然要使得不能具有如图所示的情况(三角形的位置代表放了一个马)。要避免这种情况的发生,也就是说第i行的状态右移两位或者左移两位与第i-1行的状态进行与运算的结果需要为0,那么第i-2行和第i-1行的判断方法也是一样的,下面来看一下第i行和第i-2行的马的合法状态的判断方法,也很容易想到不能出现下面这样的情况:

 ,也就是说第i行的状态左移一位或者右移一位所得到的结果与第i-2行的状态进行与运算所得结果也应该为0,满足这个条件就可以用第i-1行和第i-2对第i行进行更新了。

由此我们可以设f[i][a][b][j]表示遍历到第i行且第i行的状态为a,第i-1行的状态为b以及总共摆放了j个马的方案数,然后按照上面分析的方法进行动态转移就行了,初始化就是f[0][0][0][0]=0

结果就是把遍历到最后一行所有的方案数相加即可

下面是代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
using namespace std;
const int N=103,K=21,M=7,mod=1e9+7;
int f[N][1<<M][1<<M][K];//f[i][a][b][j]表示遍历到第i行且第i行的状态为a,第i-1行的状态为b以及总共摆放了j个马的方案数 
int count(int x)//计算x中1的个数 
{
	int cnt=0;
	while(x)
	{
		x-=(x&-x);
		cnt++;
	}
	return cnt; 
}
int main()
{
	int n,m,k;
	cin>>m>>n>>k;
	f[0][0][0][0]=1;
	for(int i=1;i<=n;i++)
	for(int c=0;c<1<<m;c++)//枚举当前行状态 
	for(int b=0;b<1<<m;b++)//枚举上一行状态 
	{
		if(c&(b<<2)||c&(b>>2)) continue;//当前行与上一行状态发生冲突 
		for(int a=0;a<1<<m;a++)//枚举上一行的上一行状态 
		{
			if(b&(a<<2)||b&(a>>2)) continue;//前两行状态发生冲突 
			if(c&(a<<1)||c&(a>>1)) continue;//当前行与上一行的上一行发生冲突 
			int t=count(a)+count(b)+count(c);
			for(int j=t;j<=k;j++)
			f[i][c][b][j]=(f[i][c][b][j]+f[i-1][b][a][j-count(c)])%mod;
		}
	}
	int ans=0;
	for(int a=0;a<1<<m;a++)
	for(int b=0;b<1<<m;b++)
		ans=(ans+f[n][a][b][k])%mod;
	cout<<ans;
	return 0;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值