POJ 2411 + POJ 2663 + POJ 3420 小方格填充之多米诺骨牌系列(状压DP)

原文链接:http://blog.csdn.net/shiwei408/article/details/8821853


解决此类问题,一般都是用1*2的小方块填充 n*m的矩阵。其中n或m两者中至少有一个较小。
解决此类的方法,当n和m都很小的时候,可以直接for循环枚举状态来更新求值。但是当二者间有一个稍大的时候,就需要构造矩阵来解决此类问题。通常将较小的那个数作为列用dfs来枚举两行之间的状态。

状态标记: 横放和竖放的下一个均为1,竖放的上一个和不放置为0 ,每行可以转化为1个2进制数。当这一行访问结束时,就会得到上一行状态,和该行状态,因为所有情况都是我们设置的,所以pre状态一定会转化为now状态。

对于每一个位置,我们有三种放置方法:1. 竖直放置2. 水平放置3. 不放置。

dfs的时候三个参数分别是 num,now,pre,分别表示当前的列数,当前行的状态,上一行的状态,初始化的时候num=now=pre = 0。每两行之间有三种状态:
1. num = num + 1, now << 1 | 1, pre << 1; // 竖直放置,当前行该列为1,上一行该列置为为0
2. num = num + 2, (now << 2) | 3, (pre<< 2) | 3; // 横放 都为11
3. num = num + 1, now << 1, pre<< 1 | 1; // 上一行该列置为1,不能竖放,不放置的状态
注意不存在上一行当前列不放置,这一行放置的情况。


DFS枚举状态时候的情况:

typedef long long LL;
int n,m,tan;
LL dp[13][1<<11];
LL matrix[11*(1<<12)][2];//注意是 11*(1<<12)
void dfs(int num,int now,int pre)
{
     if(num>m) return;
     if( num==m )
     {
         matrix[++tan][0] = pre;
         matrix[tan][1] = now;
         return;
     }
     dfs(num+1,(now<<1)|1,pre<<1);
     dfs(num+2,(now<<2)|3,(pre<<2)|3);
     dfs(num+1,pre<<1,(now<<1)|1);
     return;
}

更新答案的时候,最后的总ans就是dp[n][(1<< m)-1]的值。

        memset(dp,0,sizeof(dp));
        dp[0][(1<<m)-1] = 1;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=tan;j++)
            {
                dp[i][matrix[j][1]] += dp[i-1][matrix[j][0]];
            }
        }
        cout<<dp[n][(1<<m)-1]<<endl;

POJ2411,n,m不大于11,小范围可递推,可用矩阵枚举。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <climits>
using namespace std;

typedef long long LL;
int n,m,tan;
LL dp[13][1<<11];
LL matrix[11*(1<<12)][2];
void dfs(int num,int now,int pre)
{
     if(num>m) return;
     if( num==m )
     {
         matrix[++tan][0] = pre;
         matrix[tan][1] = now;
         return;
     }
     dfs(num+1,(now<<1)|1,pre<<1);
     dfs(num+2,(now<<2)|3,(pre<<2)|3);
     dfs(num+1,now<<1,(pre<<1)|1);
     return;
}
int main()
{
    while ( scanf("%d%d",&n,&m)!=EOF && n && m)
    {
        if( n<m ) swap(n,m); 
        tan = 0;
        dfs(0,0,0);

        memset(dp,0,sizeof(dp));
        dp[0][(1<<m)-1] = 1;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=tan;j++)
            {
                dp[i][matrix[j][1]] += dp[i-1][matrix[j][0]];
            }
        }
        cout<<dp[n][(1<<m)-1]<<endl;
    }
    return 0;
}

POJ 26633*12矩阵,代码与上类似,不再赘述。
POJ34201*2的小矩形,去拼接一个4*n(n<10^9)的矩形。N这么大递推肯定是不行了,所以我们要用矩阵快速幂进行加速,这个转移矩阵如何构造呢,我们可以直接用pre状态转移到now状态采用邻接矩阵的方式表述。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <climits>
using namespace std;

typedef long long LL;
int n,mod;
struct mat{
    LL at[16][16];
};
mat dat;

void dfs(int num,int now,int pre)
{
     if(num>4) return;
     if( num==4 )
     {
         ++dat.at[pre][now];
         return;
     }
     dfs(num+1,(now<<1)|1,pre<<1);
     dfs(num+2,(now<<2)|3,(pre<<2)|3);
     dfs(num+1,now<<1,(pre<<1)|1);
     return;
}
mat quick_pow(mat a,mat b)
{
    mat tmp; memset(tmp.at,0,sizeof(tmp.at));
    for(int i=0;i<16;i++)
    {
        for(int k=0;k<16;k++)
        {
            if( a.at[i][k])
            for(int j = 0;j<16;j++)
            {
                tmp.at[i][j] = ( tmp.at[i][j] +(a.at[i][k] * b.at[k][j])%mod) %mod;
            }
        }
    }
    return tmp;
}
mat cal(mat a,int k)
{
    if(k==1)  return a;
    mat tmp; memset(tmp.at,0,sizeof(tmp.at));
    for(int i=0;i<16;i++) tmp.at[i][i] = 1;
    if(k==0) return tmp;
    while(k)
    {
        if( k&1 )  tmp = quick_pow(a,tmp);
        k>>=1;
        a = quick_pow( a,a);
    }
    return tmp;
}
int main()
{
    memset(dat.at,0,sizeof(dat.at));
    dfs(0,0,0);
    while ( scanf("%d%d",&n,&mod)!=EOF && n && mod)
    {
        if(mod==1) {
            cout<<0<<endl;
            continue;
        }
        mat ans = cal(dat,n);
        cout<<ans.at[15][15]<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值