题目:
输入样例:
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;
}