• 骨牌摆放问题 POJ 2411(状态压缩DP)


    题目:

    给你(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必须是连续偶数个,这个可以预先缓存起来,不用每次都算。详见代码注释。

    源代码:

    #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;
    }
    

    The desire of his soul is the prophecy of his fate
    你灵魂的欲望,是你命运的先知。

  • 相关阅读:
    dojo/Deferred类和dojo/promise类的使用
    dojo中类的继承
    c# 委托
    使用Spring Data JPA报错:javax.persistence.TransactionRequiredException: Executing an update/delete query
    服务器重启后,Docker安装的mysql怎么重启?
    IDEA开启Run Dashboard的配置
    Java中List集合去重的几种方式
    关闭迅雷更新到新版本的提示
    XMind8 破解激活教程(win系统)
    连接Oracle报错 ORA-12638: 身份证明检索失败
  • 原文地址:https://www.cnblogs.com/RioTian/p/13738116.html
Copyright © 2020-2023  润新知