完全覆盖问题求解

本文详细分析了如何用2×1或1×2的骨牌完全覆盖m×n棋盘的问题。首先,介绍了问题背景和要求,然后通过逐步分析n的范围和方格数量对解的影响,探讨排列组合策略。接着,展示了3*n棋盘和m*n棋盘的代码实现,并讨论了遇到的语法和运行问题及其解决方案。最后,通过测试验证了算法的正确性,并指出对于特定尺寸的棋盘,覆盖方法数与斐波那契数列有关。

一、问题分析

(一)问题描述

用2×1或1×2的骨牌把m×n的棋盘完全覆盖是一个很容易解决的问题。那么,能不能把完全覆盖的种数求出来,会不会很复杂呢?

(二)要求

给定任意m行n列的棋盘,编写程序求出使用2×1或1×2的骨牌对棋盘进行完全覆盖有多少种不同的方法。

二、问题解答

(一)设计思路

1.问题引入

在一个n*m个方格组成的棋盘中,用1*2或者2*1两种形态的骨牌完全覆盖整个棋盘。在棋盘填充问题中,当问题规模较大时,很难想到一般思路,此时我们可以考虑从最小的问题开始分析。我们将问题简化一下,请问3*n的棋盘,有多少种完美覆盖?

2.问题分析

(1)n的范围及方格数量对解的影响

首先我们要考虑的问题是:n可以是任意正整数吗?

假如n=1,显然这个问题无解,n=3,这个问题也无解,其实这是个简单的逻辑,一个骨牌需要占两个方格,也就是说方格总数必须是2的倍数,也就是说n必须是偶数,这样才能保证3*n的棋盘的完美覆盖问题有解。

那么,只要是方格数为偶数就一定有解了吗?答案是否定的。假如我们将8*8棋盘的左上角和右下角的黑色方格同时去掉,那么还剩下62块方格,但此时一样无解。我们需要注意的是,去掉的偶数个方格中,黑色方格数(编号为0)必须等于白色方格数(编号为1),这样问题才有解!因为每个骨牌覆盖的一定是一块白色和一块黑色。

2)排列组合

我们以图1为例,看一下排列组合的方式。

首先,我们看一下3*2的棋盘有多少种排列组合方式。3*2一共就以上三种排列方式。

接着我们看一下3*4的棋盘有多少种排列方式。除了可以分为两个3*2外,还有可能如下图所示。

注意,这也是两种。(倒过来)

也就是我们可以得到:f(2)=3,f(4)=3*3+2

我们不难看出,能不能从中间切开是这个问题的重点。而切不切这是两种可能,结合以上我们的推论,我们不难得出f(n)=3*f(n-2)+2*f(n-4)+...+2*f(0)。

3.思路:

设a[i]为N=i时的方法数。i为奇数的时候肯定为0。

如果i为偶数,a[i]可以看成a[i-2]加上两个单位组成的,此时多出来的2单位有3种方法。。

也可以看成a[i-4]加上四个单位组成的,此时这四个单位一定是连在一起的,中间不能分割,所以只有两种放法。

同理,可看成a[i-6]加上六个单位组成的,此时这六个单位也连在一起,不能分割,只有两种放法…

直到所有的砖块都是连在一起的,中间不能分割,也只有两种放法。

所以

a[i]=3*a[i-2]+2*(a[i-4]+a[i-6]+…+a[0]) ①

a[i-2]=3*a[i-4]+2*(a[i-6]+…a[0]) ②

①-②,得a[i]=4*a[i-2]-a[i-4]。

由3*n棋盘覆盖方法进而得到n*m的解题思路。

(二)代码

1.3*n棋盘代码

#include

using namespace std;

int n,dp[33];

int main(){

    dp[0]=1;

    dp[2]=3;

    while(cin>>n){

        if(n==-1)break;

        if(n%2==1){

            cout<<"0"<

            continue;

        }

        for(int i=4;i<=n;i=i+2){

            dp[i]=4*dp[i-2]-dp[i-4];

        }

        cout<

    }

}

2.m*n棋盘代码

#include

using namespace std;

const int maxn=1<<12;

long long dp[2][maxn];

int cur,n,m;

void add(int a,int b){

    if(b&(1<

        dp[cur][b^(1<

}

int main(){

    while(scanf("%d%d",&n,&m)!=EOF){

        if(n

            swap(n,m);

        int all=(1<

        memset(dp,0,sizeof(dp));

        dp[0][all]=1;

        cur=0;

        for(int i=0;i

            for(int j=0;j

                cur^=1,memset(dp[cur],0,sizeof(dp[cur]));

                for(int sta=0;sta<=all;sta++){

                    if(dp[cur^1][sta]==0)

                        continue;

                    add(sta,sta<<1);                              

if(i && !(sta & ( 1<<(m-1) ) ) )                        add(sta,(sta<<1)^(1<

                    if(j && !(sta & 1) )                            add(sta,(sta<<1)^3);

                }

            }

        printf("%lld\n",dp[cur][all]);

    }

    return 0;

}

三、遇到的问题及解决方案

(一)问题:语法错误
     原因:首次运行语法错误比较多,一般都是由于粗心大意输入有误造成的,还有些错误属于変量忘记定义之类的。

解决方案:通过查找相关资料,通过代码软件进行不断调试得以更正。

(二)问题:运行后无结果
    原因:输入不恰当。

解决方案:开始输入时多加了逗号,去掉之后,即可运行并得出正确的结果。

四、算法测试

图2为3*n棋盘覆盖的测试结果,分别输入2、4、6时,结果分别为3、11、41,即当n=2、4、6时,分别有3、11、41种完全覆盖方法。图3为n*m棋盘覆盖的测试结果,当输入5 6时结果为1183,即当n*m=5*6时,有1183种使棋盘被1*2或者2*1的骨牌完全覆盖的方法。

五、结论

假定我们用F ( m , n )表示m*n棋盘覆盖方式的总数,显然,我们有F (2,1)= F (1,2)=1, F (2,2)=2。当n=3时,不难看出 F (2, n )= F (2,n-1)+ F (2,n-2)。由此可以推得数列{ F (2, n )}如下:1,2,3,5,8,13,21,34,55,...这是著名的斐波那契数列。且求解F (m,n )的一般公式十分困难,我们的算法仅能求解到(m,n)在12以内的种类数。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值