骨牌平铺问题

有一个n*m的棋盘,现在用1*2的骨牌去覆盖,问有多少种不同方法可以将这个棋盘全部覆盖。


1、状态压缩DP
枚举相邻两行的状态进行匹配转移 复杂度O( n * (2^m)^2 )
poj 2663:http://poj.org/problem?id=2663


#include <cstdio>   // poj 2663.cpp
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <utility>
using namespace std;


#define rep(i,j,k) for (int i=j;i<=k;i++)
#define Rrep(i,j,k) for (int i=j;i>=k;i--)


#define Clean(x,y) memset(x,y,sizeof(x))
#define LL long long
#define ULL unsigned long long
#define inf 0x7fffffff
#define mod %100000007


int n;


LL dp[35][8];


bool fit(int x1 , int x2 , int uplim)
{
    if ( (x1 | x2) != uplim ) return false;
    int t = x1 & x2;
    if ( t == 0 || t == 3 || t == 6 ) return true;
    return false;
}


int main()
{
    while( ~scanf("%d",&n) )
    {
        if ( n == -1 ) break;
        if ( n == 0 )
        {
            puts("1");
            continue;
        }
        if ( n & 1 )
        {
            puts("0");
            continue;
        }
        Clean(dp,0);
        dp[1][0] = dp[1][3] = dp[1][6] = 1;
        int uplim = 1<<3;
        rep(i,2,n)
            rep(j,0,uplim-1)
                rep(k,0,uplim-1)
                 if( fit(j,k,uplim-1) ) dp[i][j]+=dp[i-1][k];
        cout<<dp[n][uplim-1]<<endl;
    }
    return 0;
}

2、矩阵转移
当n和m有一个值很大 而另一个值很小(最大在7左右),我们就可以构造一个状态转移矩阵进行两行之间的转移,再用矩阵快速幂加速一下。具体hihocoder上讲的比较详细。
hihocoder 1151 1161
poj 3420: http://poj.org/problem?id=3420

<span style="font-size:24px;"><strong>#include <cstdio>  //hiho 1161.cpp
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <utility>
using namespace std;

#define rep(i,j,k) for (int i=j;i<=k;i++)
#define Rrep(i,j,k) for (int i=j;i>=k;i--)

#define Clean(x,y) memset(x,y,sizeof(x))
#define LL long long
#define ULL unsigned long long
#define inf 0x7fffffff
#define mod 12357

int uplim;
int n,k;

struct node
{
    int a[1<<7][1<<7];
    void E()
    {
        Clean(a,0);
        rep(i,0,uplim) a[i][i] = 1;
    }
    void zhuanyi()
    {
        Clean(a,0);
        dfs(0,0,0);
    }
    void dfs( int x , int y , int col ) //构造上一行状态为x,当前行状态为y  x->y的转移矩阵
    {
        if ( col == k )
        {
            a[x][y] = 1;
            return;
        }
        dfs( x<<1|1 , y<<1 , col + 1 ); //第col列不放,所以上一行已经被填充过了,而当前行还是空的
        dfs( x<<1 , y<<1|1 , col + 1 );//col列竖着放,上一行肯定没有被填充,而放过之后当前行会被填充
        if ( col + 2 <= k ) dfs( x<<2|3 , y<<2|3 , col + 2 );//在当前行的col列和col+1列横着放
    }
};

node multi( node &x , node &y ) //矩阵乘法
{
    node ans;
    rep(i,0,uplim)
        rep(j,0,uplim)
        {
            ans.a[i][j] = 0;
            rep(k,0,uplim)
                ans.a[i][j] = ( ans.a[i][j] + x.a[i][k]*y.a[k][j] ) % mod;
        }
    return ans;
}


int main()
{
    cin>>n>>k;
    if ( n < k ) swap(n,k);
    uplim = (1<<k)-1;
    node temp,ans;
    ans.E();
    temp.zhuanyi();
    while( n )
    {
        if ( n & 1 ) ans = multi( ans , temp );
        temp = multi( temp , temp );
        n>>=1;
    }
    cout<<ans.a[uplim][uplim]%mod<<endl;
    return 0;
}</strong></span>


3、扫描线

复杂度 O( n*m*2^m )

uva 11270

#include <cstdio>  //uva 11270.cpp
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <utility>
using namespace std;

#define rep(i,j,k) for (int i=j;i<=k;i++)
#define Rrep(i,j,k) for (int i=j;i>=k;i--)

#define Clean(x,y) memset(x,y,sizeof(x))
#define LL long long
#define ULL unsigned long long
#define inf 0x7fffffff
#define mod %100000007
const int maxn = 13;
LL dp[2][1<<maxn];
int n,m;

void update( int a , int b , int cur )
{
    dp[cur][b] += dp[cur^1][a];
}

void solve()
{
    int cur = 0;
    int uplim = (1<<m) - 1;
    dp[0][uplim] = 1;

    rep( i , 1 , n )
        rep( j , 1 , m )
        {
            cur ^= 1;
            Clean(dp[cur],0);
            rep( k , 0 , uplim ) //枚举上一个位置的状态
            {
                if ( k & 1<<(m-1) ) //
                    update( k , (k<<1)^(uplim+1) , cur ); //不放
                if ( i > 1 && !( k & 1<<(m-1) ) ) //竖着放
                    update( k , k<<1|1 , cur );
                if ( j > 1 && ( k & 1<<(m-1) ) && !( k & 1 ) ) //横着放
                    update( k , (k<<1)^(uplim+1)^3 , cur );
            }
        }
    printf("%lld\n",dp[cur][uplim]);
}

int main()
{
    while( scanf("%d%d",&n,&m) == 2 )
    {
        if ( n < m ) swap(n,m);
        Clean(dp,0);
        solve();
    }
    return 0;
}



3、状态压缩转移

枚举相邻两行的状态进行匹配转移 复杂度O( n * (2^m)^2 )
poj 2663
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <utility>
using namespace std;

#define rep(i,j,k) for (int i=j;i<=k;i++)
#define Rrep(i,j,k) for (int i=j;i>=k;i--)

#define Clean(x,y) memset(x,y,sizeof(x))
#define LL long long
#define ULL unsigned long long
#define inf 0x7fffffff
#define mod %100000007

int n;

LL dp[35][8];

bool fit(int x1 , int x2 , int uplim)
{
    if ( (x1 | x2) != uplim ) return false;
    int t = x1 & x2;
    if ( t == 0 || t == 3 || t == 6 ) return true;
    return false;
}

int main()
{
    while( ~scanf("%d",&n) )
    {
        if ( n == -1 ) break;
        if ( n == 0 )
        {
            puts("1");
            continue;
        }
        if ( n & 1 )
        {
            puts("0");
            continue;
        }
        Clean(dp,0);
        dp[1][0] = dp[1][3] = dp[1][6] = 1;
        int uplim = 1<<3;
        rep(i,2,n)
            rep(j,0,uplim-1)
                rep(k,0,uplim-1)
                 if( fit(j,k,uplim-1) ) dp[i][j]+=dp[i-1][k];
        cout<<dp[n][uplim-1]<<endl;
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值