前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DP专题8 | 骨牌摆放问题 POJ 2411(状态压缩DP)

DP专题8 | 骨牌摆放问题 POJ 2411(状态压缩DP)

作者头像
ACM算法日常
发布2019-07-19 18:42:35
1.4K0
发布2019-07-19 18:42:35
举报
文章被收录于专栏:ACM算法日常

题目:

给你n*m(1<=n,m<=11)的方格矩阵,要求用1*2的多米诺骨牌去填充,问有多少种填充方法。

比如下图是对于如下2x6的方格矩阵,可能的填充方案之一。

该如何使用动态规划的方式解决这道题呢?先了解一下状态压缩算法。

状态压缩通常是使用一个整数来表示一个集合,比如整数3,二进制表示为11,第一位状态为1,第二位状态为1,数字2的二进制表示为10,第一位的状态为1,第二位的状态为0,数字1的二进制表示为01,第一位为0,第二位为1,数字0的二进制可以表示为00,两位的状态都是0。

这就是说,状态可以保存在一个整数里面,对于状态压缩DP,其实也是用状态压缩后的整数表示一个维度,然后进行状态转移。

状态压缩DP:采用状态压缩算法的DP问题,也就是用整数表示集合,然后将该整数作为DP的一个维度来进行DP状态转移。

希望理解更深的童鞋能够踊跃发言指正~

继续看题,其实这道题的解法并不简单,甚至于有些晦涩,光是理解状态压缩DP并不一定能够看懂源代码。

首先定义状态转移方程:

f(i, j) = f(i-1, k)之和,j, k属于[0,1<<m)

且满足 j&k==0

满足 j|k是有连续个0的状态

f(i,j)表示第i行的状态为j时,前i行的方案总数。且定义状态1表示竖着的上面一部分,其他状态为0。

对于约束的理解是本题的关键,

约束1:j&k == 0 是因为不可能上下都是1。

约束2:j|k合并后状态0必须是连续偶数个,这个可以预先缓存起来,不用每次都算。详见代码注释。

源代码:

代码语言:javascript
复制
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <bitset>
#include <iostream>
#include <set>
#include <string>
#include <vector>

using namespace std;

long long f[12][1 << 11];
int in_s[1 << 11];

// 注意这里一定要用64位的整数
long long solve(int n, int m) {
    for (int i = 0; i < 1 << m; ++i) {
        int cnt = 0, has_odd = 0;
        // 遍历m中的每一位
        for (int j = 0; j < m; ++j) {
            if (i >> j & 1) {
                //把cnt的值先存放到has_odd上,然后清零
                has_odd |= cnt, cnt = 0;
            } else {
                //如果是偶数个0,则肯定cnt最后为0,因为0^1=1 1^1=0
                cnt ^= 1;
            }
        }
        // 如果cnt=1则表明存在连续的奇数个0位
        in_s[i] = has_odd | cnt ? 0 : 1;
    }

    f[0][0] = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < 1 << m; ++j) {
            f[i][j] = 0;
            for (int k = 0; k < 1 << m; ++k) {
                // 上面是1下面必须是0
                // 0必须是连续偶数个
                if ((j & k) == 0 && in_s[j | k]) {
                    // 满足要求后加上i-1行k的所有情况
                    f[i][j] += f[i - 1][k];
                }
            }
        }
    }

    // 最后一行的j所有位都是0
    return f[n][0];
}

int main() {
#ifdef __MSYS__
    freopen("test.txt", "r", stdin);
#endif

    int n, m;

    while (cin >> n >> m && n) {
        cout << solve(n, m) << endl;
    }
    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ACM算法日常 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档