木块砌墙---解题报告

题目详情

用 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 //提示:自动阅卷结束唯一标识,请勿删除或增加。

 
结束语:
这题有种直觉,可以更简单。。没多考虑,直接这么写了。。。有更好方法的朋友,留个言!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值