POJ 2411 状态压缩DP

98 篇文章 1 订阅

状态压缩DP

题意:

​ 有n乘m 的棋盘,现在有1乘2的方块和2乘1大小的方块,问有多少种不同的组合方式。

思路:

​ 这道题有很多种方法可以做,轮廓线dp和插头dp、状态压缩都行,先讲一下状态压缩方法。

大神的题解:http://blog.csdn.net/u013480600/article/details/19569291

两种方块方的方式有横竖两种方式可以放置,状态压缩一般压缩到二进制中用01表示,规定

0
1

为竖着放置,11为横着放置,那么,在转移的过程中必须要知道的是上一行的状态比如上一个中第k个位置是0那么当前行第k位置必须是1,所以说当把两行所有对应的关系都计算出了之后便可以枚举行依次加法即可得出答案。

​ 那么问题是如何求出这么多个对应关系,先找出规律,把不满足的情况删除,i-1行第k位置是0,那么i行第k位置是1,不是1便删除,寻找第k位置的时候是从0列开始当第0列是1的时候第1列也是1,每次保证都是与前一列无关专注于下一列的要求即可。

  • 最好是手动分析出每一种出现的可能,因为只有两种排列方式,所以也不难
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 15;

int n,m,w;
long long dp[maxn][1<<maxn];
int path[5000000][2];

void get_path()
{
    for(int i = 0;i < (1<<m); i++) {
        for(int j = 0;j < (1<<m); j++) {
            int ok = true;
            for(int k = 0;k < m; k++)
            if(ok) {
                if((i&(1<<k)) == 0) {
                    if((j&(1<<k)) == 0) {
                        ok = false;
                        break;
                    }
                }
                else {
                    if((j&(1<<k)) == 0) continue;
                    k++;
                    if(k >= m || (i&(1<<k)) == 0) {
                        ok =  false;
                        break;
                    }
                    else {
                        if((j&(1<<(k-1))) && (j&(1<<k)) == 0) {
                            ok = false;
                            break;
                        }
                        else if((j&(1<<(k-1))) == 0 && (j&(1<<k))) {
                            k--;
                        }
                    }
                }
            }
            if(ok) {
                path[w][0] = i;
                path[w++][1] = j;
            }
        }
    }
}

int main()
{
    //freopen("in.txt","r",stdin);

    while(scanf("%d%d",&n,&m) != EOF) {
        if(n+m == 0) break;
        if(n<m) swap(n,m);
        w = 0;
        get_path();
        memset(dp,0,sizeof(dp));
        dp[0][(1<<m)-1] = 1;
        for(int i = 0;i < n; i++) {
            for(int j = 0;j < w; j++) {
                dp[i+1][path[j][1]] += dp[i][path[j][0]];
            }
        }
        printf("%I64d\n",dp[n][(1<<m)-1]);
    }

    return 0;
}

上一种状态压缩的方式很暴力,很容易超时。。。

思考上一个方法发现是对前一行的每一种状态都进行了枚举,两重循环的暴力枚举使得数量级急剧增加,思考更好的方式!

  • 对于每两行的状态,我们要求的就是临近的两行,可不可以直接找出这两行?可以的,只有三种方式:dfs即可。

当前位不放,则前行的当前位必定为1才能兼容且后行为0:c=c+1, [(pre<<1)|1,now<<1]

当前位上方,则前行的当前位必定为0才能兼容且后行为1:c=c+1, [(pre<<1),(now<<1)|1]

当前位右方,则前行的当前2位必定为1才能兼容且后行当前2位为1:c=c+2, [(pre<<2)|3,(now<<2)|3]

且要注意到:如果执行以上操作而使得c==m,则表明生成了一个兼容对。如果c>m,则表明生成了一非法的长度越界兼容对,要抛弃。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int maxn = 15;

int n,m,w;
int path[500000][2];
long long dp[maxn][1<<maxn];

void dfs(int pre,int now,int c)
{
    if(c > m) return ;
    if(c == m) {
        path[w][0] = pre;
        path[w++][1] = now;
        return ;
    }
    dfs((pre<<1)|1,now<<1,c+1);
    dfs(pre<<1,(now<<1)|1,c+1);
    dfs((pre<<2)|3,(now<<2)|3,c+2);
}

int main()
{
//    freopen("in.txt","r",stdin);

    while(scanf("%d%d",&n,&m) != EOF) {
        if(n+m==0) break;
        if(m>n) swap(n,m);
        w = 0;
        dfs(0,0,0);
        memset(dp,0,sizeof(dp));
        dp[0][(1<<m)-1] = 1;
        for(int i = 0;i < n; i++) {
            for(int j = 0;j < w; j++) {
                dp[i+1][path[j][1]] += dp[i][path[j][0]];
            }
        }
        printf("%I64d\n",dp[n][(1<<m)-1]);
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值