[2018蓝桥杯B组决赛] E-搭积木

题解

  该题考察经典算法,可我还是太菜了...,不会做/(ㄒoㄒ)/~~。

       正解应该是 dp + 前缀和优化,只解 n == 1 或暴力 dp 都会超时,怎么看出来的呢?

       dp:

  

  f[i][j][k] 表示第 i 层在 [j, k] 区间搭积木的总方案数,dp 方程很显然是 f[i][j][k] = f[i][j][k] + f[i - 1][x][y]; 其中 [j, k] 区间不存在不可放的位置,这是暴力 dp 的做法,时间复杂度为 $O(n^5)$。

       前缀和优化:

       在上述图示中,第 i - 1 层 f[i - 1][x][y] 即 [x, y] 这一部分很显然可以用前缀和优化掉,只用了 $O(1)$的时间复杂度即可完成,二维前缀和初始化的公式为:s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j],求解子矩阵为:s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1],不熟悉的可以画图推一下,比较容易理解。

       时间复杂度为 $O(n^3)$,完全可以过。  

#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;
}

 

posted @ 2020-11-13 00:20  Fool_one  阅读(235)  评论(0编辑  收藏  举报