状态压缩dp入门-蒙德里安的梦想

题目描述

在这里插入图片描述

输入描述

在这里插入图片描述

输出描述

在这里插入图片描述

数据范围

在这里插入图片描述

输入样例

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

本题中,采用了先放横的小方块,再在空位插入竖的小方块的解法,此时总方案数即为只放横着小方块的合法方案数。原因在于:对于某种放置完横的小方块的情况后,插入竖的小方块的方法一定是唯一的。因此本题可转化为在按某种方法放置了横的小方块后,剩余空位是否满足能够插满若干竖的小方块,满足该情况的方法数总和即为总方案数。

由于竖小方块只能在同一列内进行摆放,因此在摆放横小方块时,显然合法方案是在该列留下偶数个(也可以是0个)空格。

因此,我们可以对每一列中的合法状态进行一个预处理。其中,由于该列中的每一行都可以有两种情况,即:存在方块(1)或空格(0),因此 n 行共有 2 n 2^n 2n 种方案。

//每一列合法状态预处理
	for (int i = 0; i < 1 << n; i ++){
    
    //此处的i表示的即为状态 若n=5 则由00000判断至11111
        int cnt = 0;
        st[i] = true;
        for (int j = 0; j < n; j ++)//表示行数
            if (i >> j & 1){
    
    //即取该状态的第j行的数
                if (cnt & 1) st[i] = false; //cnt为在这位1之前存在了多少连续的0 若为奇数说明之前的空位不能插入竖方块
                cnt = 0;//清零 用于记录下一段连续0的数量
            }
            else cnt ++;
        if (cnt & 1) st[i] = false; //判断最后一段有多少个连续的0 若11110则不合法
    }

对单列的合法情况处理完后,即可开始处理多列状态。不难发现横着的小方块需要占据两列的特殊性,为了进行规范,我们统一将某个横小方块的左侧所处的列的对应行标为 1 ,例如:

在这里插入图片描述

由图中可看出,第 i - 1 列和第 i 列的第一行中,红蓝色的小方块发生了冲突,这是由于我们默认将每个横小方块的左侧所在列作为标记点,但小方块的右侧也是会占据空间的。因此对于每一列,在考虑该列的合法状态的同时也需要兼顾上一列的对应行是否为 1,即两个相邻列的同一行不能够同时为 1,否则将出现方块重叠的情况。

同时还可以注意到,对于第 i 列,在兼顾了第 i - 1 列的状态下,此时的状态为 1101,显然对于该状态是无法在后续进行插入竖的小方块的。因此对于每一列,还需要考虑当列在兼顾了上一列的状态之后,是否符合之前预处理的合法状态。

因此,对于目前遍历到的某一列 i,可枚举该列和上一列的所有可能情况,并判断这两列的可能情况进行叠加后是否合法,若合法,则状态转移方程为 f [ i ] [ j ] += f [ i - 1] [ k ] ,即第 i 列的第 j 种状态由第 i - 1 列的第 k 中状态转移而来,注意累加在第 i + 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])) 
            //j&k==0 表示相邻两列的同一行不存在同时为1
            //st[j|k] 意为判断第i-1列的k状态与第i列的j状态融合后是否满足先前的预处理结果
               f[i][j] += f[i - 1][k];  //状态转移方程    

参考代码

#include<bits/stdc++.h>
using namespace std;
const int N = 12, M = 1 << N;
int st[M];
long long f[N][M];


int main(){
    
    
    int n, m;
    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;
                    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 - 1][k];      
        cout << f[m][0] << endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/laysan/article/details/121172784
今日推荐