铺砖问题 (状态压缩DP)

题意:

给定n*m的格子,每个格子被染成了黑色或者白色。现在要用1 * 2 的砖块覆盖这些格子,要求块与块之间互相不重叠,且覆盖了所有白色的格子,但不覆盖任意一个黑色格子。求一个有多少种覆盖方法,输出方案数对M取余后的结果。

输入:

n= 3

m= 4

每个格子的颜色如下所示(.表示白色,x表示黑色)

.x.

输出:

2

铺砖问题 (状态压缩DP)_第1张图片

分析:

由于黑色的格子不能被覆盖,因此used里对应的位置总是false。对于白色的格子,如果现在要在(i,j)位置上放置砖块,那么由于总是从最上方的可放的格子开始放置,因此对应(i’,j’)<(i.j)(按字典序比较)的(i’,j’)总有 used[i’][j’]=true成立。

此外,由于砖块的大小为1*2,因此对于每一列j’在满足(i’,j’)>=(i.j)的所有i’中,除了最小的i’之外都满足used[i’][j’]=false。因此,不确定的只有每一列里还没有查询的格子中最上面的一个,共m个。从而可以把这m个格子通过状态压缩编码进行记忆化搜索。按照之前的状态压缩DP的写法就得到了下面的程序。

int dp[1 << maxn];      //DP数组(滚动数组循环利用)

void solve()
{
    int *crt = dp[0], *next = dp[1];
    crt[0] = 1;
    for (int i = n - 1; i >= 0; i--){
        for (int j = m - 1; j >= 0; j--){
            for (int used = 0; used < 1 << m; used++){
                if ((used >> j & 1) || color[i][j]){
                    //不需要在(i, j)放置砖块
                    next[used] = crt[used & ~(1 << j)];
                }
                else{
                    //尝试2种放法
                    int res = 0;
                    //横着放
                    if (j + 1 < m && !(used >> (j + 1) & 1) && !color[i][j + 1]){
                        res += crt[used | 1 <<< (j + 1)];
                    }
                    //竖着放
                    if (i + 1 < n && !color[i + 1][j]){
                        res += crt[used | 1 << j];
                    }
                    next[used] = res % M;
                }
            }
            swap(crt, next);
        }
    }
    printf("%d\n", crt[0]);
}


你可能感兴趣的:(OJ,挑战程序设计竞赛(第二版))