状态压缩DP----蒙德里安的梦想

题目

求把 N×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。

例如当 N=2,M=4 时,共有 55 种方案。当 N=2,M=3 时,共有 33 种方案。

如下图所示:

2411_1.jpg

输入格式

输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N 和 M。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。

输出格式

每个测试用例输出一个结果,每个结果占一行。

数据范围

1≤N,M≤11

输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205

题目源头:291. 蒙德里安的梦想 - AcWing题库

思想

状压 dp 就是采用二进制数保存状态,方便进行位运算操作。例如 八皇后、八数码问题也都是采用了状态压缩的思想来使用一个二进制数唯一对应集合中的一个状态。 

关键是要体会采用二进制数来表示状态的思想,要转变传统思维,学习接收并吸收这种思想

解题思路:方块是固定的1*2只有两种摆法,横着摆或者竖着摆。我们只需要将所有横着摆的位置求出,那么竖着摆的位置也固定了下来,前提是横着摆放的位置是合法的。

解题前言

如何表示状态,f ( i , j ) 都代表什么意思

 i  指的是第 i 列 

 j   指的是第 i  列中各个行有无第 i  - 1 列 捅进来的二进制表达式;

因为题中的方块横着 必然是左边一个正方形右边一个正方形 为了合理安排数据的表示,j----记录横着的方块的右边部分,即在第 i 列上有 横着的方块的第二个正方形就在相应的位置记录成 1。

例如:

我们可以得到第  i  列的  j =(1010100)=84 那么就是 f ( 1 , 84 ) 

状态DP解题思路

1. 状态表示---1.1 集合  1.2属性

2.状态计算

状态表示:f(i,j):表示第 i   -  1 列捅到第 列的情况是 j 时,前 i 列的所有的摆法。

可以用1表示捅进来,0表示没有捅进来。想要表示捅过来的情况有点难,但是注意到每个格子只有捅过来和不捅过来这两种情况,所以可以考虑用状态压缩,也即对位进行操作,第几位代表第几列,这一位上是1代表有捅过来的,0代表没有捅过来的。

有了这个我们就要考虑怎么进行状态转移了,现在只看第 列和第 i - 1 列.要求f ( i , j ),表示第i - 1列的情况是 j,想要计算出这种情况下所有的填发,就要找到所有的第i - 2列捅到第i - 1列的所有合法的填法,第 i - 2列捅到第i - 1列所有合法的填法用 f(i - 1, k ) 表示,那么找到合法的k 就可以进行状态计算:

f(i , j ) = f(i , j) + f(i - 1, k )

这个公式表达的含义:在第 i 列中的合法填法 j 同时满足第 i - 1列合法序列 k ,同一个j 累加i - 1的不同的合法序列 k 累加到底,意思就是f( i, j): 第 i 列,序列为 j    前 i - 1的所有摆法。所以最后的答案就是第m 列的 所有合法的 j 的 累加这时我们可以注意到 从 在m 列中是不会有方块横着捅入第m + 1列的所以 不存在的第m + 1列的合法序列一定是0。根据上面的公式,我们可以把m 列中所有合法的j 收敛到第m + 1列 f[ m ] [ 0 ] 。

属性:

1.同一列中不能出现10001的情况(不能两个1中间空着奇数个空白块,因为这样的话,竖着的方块不能完美插入)。进行代码初始化,找出所有符合该条件的序列,标记成true。

2.因为横着的方块是分为左方块和右方块,我们为了统一化二进制,只用了右方快进行二进制表示,但是一列中被方块占用的格子不仅有左方块还有右方块,可以用  j | k  表示一列中真正被占用格子的真是情况。j表示第i列的序列,k表示第i - 1列的序列。

3.不能出现第i - 1列被捅进来,第i列又被捅进来的情况。

所以要保持 j & k == 0 第 i  - 1 列有“行”为 1(被i - 2列捅进来) 第 i - 2列对应行必须为 0;反之亦然。

实现代码:

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

using namespace std;

const int N = 12, M = 1 << N;

int n, m;
long long f[N][M];
bool st[M];

int main()
{
    while(cin>>n>>m, n|m)
    {
        for(int i = 0; i < 1 << n; i ++)
        {
            int cnt = 0;
            st[i] = true;
            for(int j = 0; j < n; j ++)
            {
                if(i >> j & 1)
                {
                    if(cnt & 1) st[i] = false;
                    else cnt = 0;
                }
                else cnt ++;
                
            }if(cnt & 1) st[i] = false;
        }
        memset(f, 0, sizeof f);
        
        f[0][0] = 1;
        for(int i = 1; i <= m ; i ++)
            for(int j = 0; j < 1<<n; j ++)
                for(int k = 0; k < 1 <<n; k ++)
                {
                    if((j&k) == 0 && st[j|k])
                        f[i][j] = f[i][j] + f[i - 1][k];
                }
        cout<<f[m][0]<<endl;
    }
    return 0;
}

借鉴文章:

【状态压缩dp】蒙德里安的梦想、最短Hamiltona路径_状态压缩dp 蒙-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值