【SJTUOJ笔记】P1092 小F的地板

这篇博客详细介绍了如何使用状态压缩和递归方法解决SJTUOJ上的P1092问题,即用1×2和2×1的方块覆盖m×n的平面。博主通过简化问题,提出边界条件,并解释了处理2×2方块缺失一角情况的策略,讨论了递归过程中可能遇到的特殊情况和避免重复的方法。文章还提供了小数据的示例以供调试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://acm.sjtu.edu.cn/OnlineJudge/problem/1092
我们先来看一看这个问题的简化版本:只用 1×2 1 × 2 2×1 2 × 1 两种方块覆盖 m×n m × n 的平面。
首先,状态压缩是毋庸置疑的。若某个方块被覆盖则为1,没有被覆盖则为0。这样,每一行的状态可以用一个二进制数来表示,且其转化为十进制的大小不超过 29=512 2 9 = 512
为了下文扩展到当前问题,这里用递归来描述解法。由于每个方块最多影响两行,递归时需要的行参数只有当前行和上一行。但是,递归是对列从左往右进行的。具体思路我们在代码中说明。

//设m是总行数,n是总列数
void dp(int row, int col, int now, int last){
//row表示当前是第几行,col表示当前是第几列,now和last分别表示当前行和上一行的状态。
    if (col > n)
        return;
    if (col == n){
        f[row][now] += f[row - 1][last];
        //f[i][j]表示前i-1行已经完全覆盖,第i行覆盖的状态为j(用二进制状态压缩)的方法数。
    }
    dp(row, col + 1, (now << 1) | 1, last << 1); //放2*1的方块
    /*以这个状态转移为例解释一下参数变化的含义。当前行不变,还是row;2*1的方块只占一列,因此col+1;放过之后,now的这一列是被覆盖的,因此把now右端添加1;能放2*1的方块要求上一行的这个位置本来没有被覆盖,因此把last右端添加0。*/
    dp(row, col + 2, (now << 2) | 3, (last << 2) | 3); //放1*2的方块
    /*如果放1*2的方块但上一行对应的位置没有被覆盖的话,以后就没有机会再去覆盖了,不符合题意。因此last右端添加两个1。*/
    dp(row, col + 1, now << 1, (last << 1) | 1); //不放。同上,上一行的这个位置必须已被覆盖。
}

边界条件:f[0][i] = 0, f[0][(1 << n) - 1] = 1。表示第0行事先视为全部铺满,若搜索第一行时出现2*1的方块越界的情况,last(代表第0行)中会出现0,最后进行累加时必然是加0。最后答案是f[m][(1 << n) - 1]
以上做法的关键在于理解:我们是在铺当前行,上一行要正好留出空缺。


有了上面的基础,我们再来考虑如何处理要求的变化:缺一角的 2×2 2 × 2 方块。经过多次尝试,我发现不能再要求当前行和上一行始终处于同一列;但是,列数差也不能超过 2 2 。因此,我多加两个只能取0 1 1 递归参数exNowexLastexNow=1表示当前行比上一行多一列,exLast=0表示上一行比当前行多一列。递归的思路没有什么变化,不过情况变得很复杂,还要小心重复。具体分类在代码中说明。

void dp(int row, int col, int now, int last, int exNow, int exLast){
    if (col > n || (col == n && (exNow || exLast))) //超出边界
        return;
    if (col == n && exNow == 0 && exLast == 0){ //完全合适
        f[row][now] += f[row - 1][last];
        return;
    }
    if (exNow == 0 && exLast == 0){ //正常情况
        dp(lim, col + 1, (now << 1) | 1, last << 1, 0, 0); //2 * 1
        dp(lim, col + 2, (now << 2) | 3, (last << 2) | 3, 0, 0); //1 * 2, exNow = 0
        dp(lim, col + 1, (now << 2) | 3, (last << 1) | 1, 1, 0); //1 * 2, exNow = 1
        dp(lim, col + 1, (now << 1) | 1, last << 2, 0, 1); //右下缺口, exLast = 1
        dp(lim, col + 2, (now << 2) | 2, last << 2, 0, 0); //右下缺口, exLast = 0
        dp(lim, col + 2, (now << 2) | 1, last << 2, 0, 0); //左下缺口
        dp(lim, col + 1, (now << 2) | 3, last << 1, 1, 0); //右上缺口, exNow = 1
        dp(lim, col + 2, (now << 2) | 3, (last << 2) | 1, 0, 0); //右上缺口, exNow = 0
        dp(lim, col + 2, (now << 2) | 3, (last << 2) | 2, 0, 0); //左上缺口
        dp(lim, col + 1, now << 1, (last << 1) | 1, 0, 0); //不放, exNow = 0
    }
    else if (exNow == 1 && exLast == 0){ //当前行超出一列
        dp(lim, col + 2, (now << 1) | 1, last << 2, 0, 0); //左下缺口
    }
    else{
        dp(lim, col + 1, (now << 2) | 3, last, 1, 0); //1 * 2, exNow = 1
        dp(lim, col + 2, (now << 2) | 3, (last << 1) | 1, 0, 0); //1 * 2, exNow = 0
        dp(lim, col + 2, (now << 2) | 3, last << 1, 0, 0); //左上缺口
    }
}

读者可以思考一下,为什么没有这几种情况:
1、不放,exNow = 1exLast = 1
2、exNow = 1时,缺口不放, col + 1, exNow = 0
3、exLast = 1时,缺口视为已放, col + 1, exLast = 0
当然,如果把以上情况添加进去,就必须把另一些情况去除来避免重复。
这里把小数据的答案给出,供调试用。

mnans
235
2411
2524
2653
338
3455
35140
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值