题目详情
用 1×1×1, 1× 2×1以及2×1×1的三种木块,
搭建K × 2^N × 1的墙,不能翻转、旋转(0<=N<=1024,1<=K<=5)
有多少种方案,输出结果对1000000007取模。
举例:
举个例子如给定N=1 K=2
答案是7,如下图所示
答题说明
main函数可以不实现,完成给定的子函数calculate即可(之所以给出main,在于方便你在线编译测试)
==================================================================================================================================
首先,我们把墙倒过来放,即K在下边,这样得到的结果会和原来的墙一模一样,原因很明显就不证了,倒过来是为了降低时间复杂度。
思路:
每一层有一个状态值,该值起决于下一层有那几个位置放了2*1*1的木块。
比如一层有5个位置(倒过来后,K在下边,K最大为5) 第x-1层 只有第1,3位置放了 2*1*1的木块,那么第x层的状态就可以表示为10100既20、显然每层可能的状态值就是0到2^k-1
一旦某一层的状态值确定了(也就是该层有那几个位置被下一层占了),就可以根据这个状态值,搜出所有该状态值下的放木块的方法,而每一种方法,又确定了上一层的状态值。
由此:
我们设 F(x, y) 为从上往下x行, 且x行的状态值为y的总的摆放方案
比如F(2,1) 表示从上往下数2行(既包含第1行和第2行),且第2行的第一个位置(10000)被第3行占了的情况了 可以摆放的方案数。
状态转移式:
F(x, y) = F(x-1, h(1)) + F(x-1, h(2)) + F(x-1, h(3)) + ......+F(x-1,h(g(y)));
其中g(y) 表示一行中,状态值是y时,该行所有摆放的方案数
每个h都是表示一种该层的摆放方式所确定的上一层的值。比如改行在第1,3位置摆了2*1*1,其他都摆放了1*1*1,那么上一层的状态值就是20
答案就是F(2*n, 0);
特殊情况:
当最顶上一层的状态值为2^k-1时,该层的摆放方案是0。
在最顶上一层的上边有个虚拟层,该层所有状态值下的F都为1.(便于计算的)
代码:
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
#ifdef WIN32
#define ll __int64
#else
#define ll long long
#endif
int stan = 0,stak= 0;
ll f[3000][1<<6];
//将二进制转成十进制
ll bit2int(int bit[], int k)
{
ll i=1;
ll ans = bit[0];
for (i = 1; i < k ;i++)
ans += bit[i] * (1<<i);
return ans;
}
//十进制转成2进制
void int2bit(int bit[], ll num)
{
memset(bit,0,sizeof(bit));
ll i;
for (i = 0; num != 0 ; i++)
{
bit[i] = num % 2;
num/=2;
}
}
ll all = 0;
//计算从上往下到第x行(包含上边所有行) 以x状态为y 时 的每种一种放法特例所得到的f(x-1,h),加到all上去
void count(ll x,int bit[],ll n,int bit2[]);
//计算从上往下到第x行(包含上边所有行) 以x状态为y 时的方法总数
int dp(ll x,ll y)
{
if (x == 1 && y == (1<<stak )-1) return 0;
if (x == 0) return 1;
if (f[x][y] != 0) return f[x][y];//已经计算过了
all = 0;
//第x行的2进制表示
int bit[6];
int2bit(bit,y);
//第x-1行的2进制表示
int bit2[6];
memset(bit2,0,sizeof(bit2));
//计算
count(x,bit,0,bit2);
return (f[x][y] = all);
}
//第x行,从第0到n位的都放过了
void count(ll x,int bit[],ll n,int bit2[])
{
//递归边界
if (n>=stak)
{
//将得到的bit2[]转成状态h,就可以得到f(x-1,h). 加到 all上面去
all = (all + dp(x-1,bit2int(bit2,stak))) % 1000000007 ;
return ;
}
//这位被占了,那么这位不能放东西,且x-1的这位,必然是0
if (bit[n] == 1)
{
bit2[n]=0;
count(x,bit,n+1,bit2);
}
//如果这位是空的,那么3种方块都有可能放
else
{
//放1*1*1
bit2[n] = 0;
count(x,bit,n+1,bit2);
//放2*1*1
bit2[n] = 1;
count(x,bit,n+1,bit2);
//放1*2*2 条件是这一位置不是最后一位,且下一位置可以放
if (n<stak && bit[n+1] == 0 )
{
bit2[n] = 0;
bit2[n+1] = 0;
count(x,bit,n+2,bit2);
}
}
}
int calculate(int n,int k)
{
stak = k;
stan = n;
memset(f,0,sizeof(f));
return dp(2*n,0);
}
//start 提示:自动阅卷起始唯一标识,请勿删除或增加。
int main() {
//自行定义n,k
int n,k;
//为了调试,实际提交要去掉这行
while(1)
{
scanf("%d%d",&n, &k);
printf("%d\n",calculate(n,k));
}
return 0;
}
//end //提示:自动阅卷结束唯一标识,请勿删除或增加。
结束语:
这题有种直觉,可以更简单。。没多考虑,直接这么写了。。。有更好方法的朋友,留个言!