[蓝桥杯 2018 国 B] 搭积木 (区间dp + 二维前缀和优化)

原题链接(洛谷)

题目描述

小明对搭积木非常感兴趣。他的积木都是同样大小的正立方体。

在搭积木时,小明选取 m m m 块积木作为地基,将他们在桌子上一字排开,中间不留空隙,并称其为第 0 0 0 层。

随后,小明可以在上面摆放第 1 1 1 层,第 2 2 2 层,……,最多摆放至第 n n n 层。摆放积木必须遵循三条规则:

规则 1 1 1:每块积木必须紧挨着放置在某一块积木的正上方,与其下一层的积木对齐;

规则 2 2 2:同一层中的积木必须连续摆放,中间不能留有空隙;

规则 3 3 3:小明不喜欢的位置不能放置积木。

其中,小明不喜欢的位置都被标在了图纸上。图纸共有 n n n 行,从下至上的每一行分别对应积木的第 1 1 1 层至第 n n n 层。每一行都有 m m m 个字符,字符可能是 .X,其中 X 表示这个位置是小明不喜欢的。

现在,小明想要知道,共有多少种放置积木的方案。他找到了参加蓝桥杯的你来帮他计算这个答案。

由于这个答案可能很大,你只需要回答这个答案对 1000000007 ( 1 0 9 + 7 ) 1000000007(10^9+7) 1000000007(109+7) 取模后的结果。

注意:地基上什么都不放,也算作是方案之一种。

n ≤ 100 n \le 100 n100 m ≤ 100 m \le 100 m100

题解

很多题解都不是很清晰,所以准备写一篇详细一点的。

一开始没有注意规则二,即同一层之间摆放的格子必须是连续的。这个点很关键。

朴素的 DP 方程

状态表示

f [ i ] [ l ] [ r ] f[i][l][r] f[i][l][r] :摆了 i i i 层,最下面一层(第 i i i 层)摆放的区间为 [ l , r ] [l,r] [l,r] 的方案数量。

显然答案为所有的 f [ i ] [ l ] [ r ] f[i][l][r] f[i][l][r] 之和。

状态转移

有一些状态是不合法的,即会和“小明不喜欢的区域”冲突。当前 i i i 行第 l . . . r l...r l...r 列中有小明不喜欢的位置,那么 f [ i ] [ l ] [ r ] f[i][l][r] f[i][l][r] 一定为 0 0 0。可以预处理“不喜欢的区域”的前缀和,实现 O ( 1 ) O(1) O(1) 查询 l . . r l..r l..r 的前 i i i 行有没有“不喜欢的区域”。

对于合法的情况,有 f [ i ] [ l ] [ r ] = ∑ l ≤ x ≤ y ≤ r f [ i − 1 ] [ x ] [ y ] f[i][l][r]=\sum_{l\le x\le y\le r}f[i-1][x][y] f[i][l][r]=lxyrf[i1][x][y]。这个也比较好理解,因为第 i i i 层必须完全“拖住”第 i − 1 i-1 i1 层。

暴力转移时间复杂度为 O ( n m 4 ) O(nm^4) O(nm4)

优化

技巧是利用二维前缀和

观察从第 i − 1 i-1 i1 行向第 i i i 行转移的过程:

对所有的 [ l , r ] [l,r] [l,r],求 ∑ l ≤ x ≤ y ≤ r f [ i − 1 ] [ x ] [ y ] \sum_{l\le x\le y\le r}f[i-1][x][y] lxyrf[i1][x][y]

首先当 y < x y<x y<x 的时候, f [ i − 1 ] [ x ] [ y ] f[i-1][x][y] f[i1][x][y] 显然等于0,所以我们只需要限制 x ≤ r x\le r xr y ≥ l y\ge l yl 即可。上面那个式子可以写成 ∑ x = l m ∑ y = 1 r f [ i − 1 ] [ x ] [ y ] \sum_{x =l}^m \sum_{ y=1}^r f[i-1][x][y] x=lmy=1rf[i1][x][y]

如果我们把 ( x , y ) (x,y) (x,y) 看成平面上的一个点,那么所有满足要求的 1 ≤ x ≤ r 1\le x \le r 1xr 并且 l ≤ y ≤ r l\le y\le r lyr 的点 ( x , y ) (x,y) (x,y) 其实形成了一个矩形。左下角坐标为 ( 1 , l ) (1,l) (1,l),右上角坐标为 ( r , m ) (r,m) (r,m)

因此,从第 i − 1 i-1 i1 行向第 i i i 行转移的过程,就是对所有满足要求的 ( l , r ) (l,r) (l,r) ,求以 ( 1 , l ) (1,l) (1,l) 为左下角、以 ( r , m ) (r,m) (r,m) 为右上角的矩形内的 f i − 1 f_{i-1} fi1 值之和。我们可以在求完所有的 f i − 1 f_{i-1} fi1 后,用 O ( m 2 ) O(m^2) O(m2) 的代价预处理一遍二维前缀和,再 O ( m 2 ) O(m^2) O(m2) 枚举所有的 ( l , r ) (l,r) (l,r) 并用 O ( 1 ) O(1) O(1) 的时间完成每次回答。

总的时间复杂度为 O ( n m 2 ) O(nm^2) O(nm2),空间复杂度可以通过滚动数组降到 O ( m 2 ) O(m^2) O(m2)

代码就贴一下yxc的)

#include <iostream>
using namespace std;

typedef long long LL;
const int N = 110, mod = 1e9 + 7;
int n, m;
LL f[N][N][N];
LL s[N][N], c[N][N];

// 前缀和 
void get_presum(int i)
{
    // 正方形的前缀和,都从 1 开始 
    for (int j = 1; j <= m; ++j) {
        for (int k = 1; k <= m; ++k) {
            s[j][k] = (s[j - 1][k] + s[j][k - 1] - s[j - 1][k - 1] + f[i][j][k]) % mod;
        }
    }
}

// 子矩阵 
int submatrix(int x1, int y1, int x2, int y2)
{
    return (s[x2][y2] - s[x1 -  1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]) % mod;
}

int main()
{
    cin >> n >> m;
    char str[N];
    // 自下往上(倒置过来) 
    for (int i = n; i; --i) {
        cin >> str + 1;
        for (int j = 1; j <= m; ++j) {
            c[i][j] = c[i][j - 1]+ (str[j] == 'X');
        }    
    } 
    f[0][1][m] = 1;
    get_presum(0);
    LL ans = 1;
    // dp 按行求解方案数 
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            for (int k = j; k <= m; ++k) {
                if (c[i][k] - c[i][j - 1] == 0) {
                    // 注意下标变换 (1, k) 和 (j, m) 
                    f[i][j][k] = (f[i][j][k] + submatrix(1, k, j, m)) % mod;
                    ans = (ans + f[i][j][k]) % mod;
                }
            }
        }
        get_presum(i);
    }
    cout << (ans + mod) % mod << endl;
    return 0;
}
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值