【SJTUOJ笔记】P1092 小F的地板

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 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
当然,如果把以上情况添加进去,就必须把另一些情况去除来避免重复。
这里把小数据的答案给出,供调试用。

m n ans
2 3 5
2 4 11
2 5 24
2 6 53
3 3 8
3 4 55
3 5 140

你可能感兴趣的:(算法与数据结构)