【动态规划dp+前缀和】 [蓝桥杯2018决赛]搭积木

题目描述

小明对搭积木非常感兴趣。他的积木都是同样大小的正立方体。
在搭积木时,小明选取 m 块积木作为地基,将他们在桌子上一字排开,中间不留空隙,并称其为第0层。
随后,小明可以在上面摆放第1层,第2层,……,最多摆放至第n层。摆放积木必须遵循三条规则:
规则1:每块积木必须紧挨着放置在某一块积木的正上方,与其下一层的积木对齐;
规则2:同一层中的积木必须连续摆放,中间不能留有空隙;
规则3:小明不喜欢的位置不能放置积木。
其中,小明不喜欢的位置都被标在了图纸上。图纸共有n行,从下至上的每一行分别对应积木的第1层至第n层。
每一行都有m个字符,字符可能是‘.’或‘X’,其中‘X’表示这个位置是小明不喜欢的。
现在,小明想要知道,共有多少种放置积木的方案。他找到了参加蓝桥杯的你来帮他计算这个答案。
由于这个答案可能很大,你只需要回答这个答案对1000000007(十亿零七)取模后的结果。
注意:地基上什么都不放,也算作是方案之一种。

输入
输入数据的第一行有两个正整数n和m,表示图纸的大小。n<=100,m<=100
随后n行,每行有m个字符,用来描述图纸 。每个字符只可能是‘.’或‘X’。
输出
输出一个整数,表示答案对1000000007取模后的结果。
样例输入
2 3
…X
.X.
样例输出
4


思路

这是一道看了b站大佬讲解才懂做的题,b站大佬视频指路:b站讲解
下面是我看了这么多题解之后自己的理解:

  1. 首先,我们可以利用递推思想通过从第0层只有一种放置方案(第0层只能全放满),一步步递推到第n层计算出总共拥有多少种放置方案。我们定义数组dp[n][m][m],dp[i][j][k]表示第i行的j至k列必放积木时,总共有多少种放置方案。如:初始化dp[0][1][m]=1表示第0层1至m列只有一种摆放方式,即全部摆满。
  2. 上面讲了dp数组的初始化,这里讲dp数组的递推:dp[i][j][k]表示的是第i行的j至k列必放积木的方案总数,这表示i-1行除了j-k必放积木外,其它都是可以选择放不放积木的,如下图:
    在这里插入图片描述

可得,dp[i][j][k]由:
  for x in [1, j):
    for y in (k, m]:
      dp[i][j][k] += dp[i-1][x][y]

但是算一下i,j,k,x,y五重循环…会超时,这里就有一种解决方法,解决掉x,y这个二重循环,从而把整个过程变成三重循环。我们可以根据求二维前缀和的知识把求dp[i][j][k]转化为下图,其中s[m][m]数组用于存储前缀和,s[i][j]表示从第i列到第j列有多少种方案:
在这里插入图片描述

由上图可得我们可以将dp[i][j][k]的求解转化为求蓝色框的公式:
dp[i][j][k] = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1] = s[j][m] - s[1-1][m] - s[j][k-1] + s[1-1][k-1]
3. 为解决判断第i行j至k列是否能摆满积木(没有’X’)的问题,我们可以定义数组a[n][m], 用以记录第i行从0到j列为止,总共出现了多少个’X’,初始化a[i][0] = 0, 递推式为:a[i][j] = a[i][j-1] + (输入==‘X’),即该位置输入如果是’X’则+1;这样我们判断第i行j至k列是否能摆满直接判断a[i][k]-a[i][j-1]是否等于0就行了,如果等于0说明i行j至k列中间没有’X’,可以摆满
4.详细步骤见代码

代码

#include<bits/stdc++.h>
#define N 110
#define ll long long
#define mod 1000000007
using namespace std;
int n, m;
ll a[N][N] = {0}, s[N][N] = {0}, dp[N][N][N] = {0}, ans = 1;

//计算二维前缀和
void sum(int r){
    for(int i=1; i<=m; i++){
        for(int j=1; j<=m; j++){
            s[i][j] = (s[i][j-1] + s[i-1][j] - s[i-1][j-1] + dp[r][i][j])%mod;
        }
    }
}

//将dp[i][j][k]转化为二维前缀和计算方法
ll cal(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 x;

	//根据计算a数组
    for(int i=n; i>=1; i--){
        for(int j=1; j<=m; j++){
            cin>>x;

            a[i][j] = a[i][j-1] + (x=='X');
        }
    }

	//初始化dp数组第0行1至m列只有一种摆法:全部摆满
    dp[0][1][m] = 1;
    //先求第0行的二维前缀和
    sum(0);

    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            for(int k=j; k<=m; k++){
            //判断第i行j至k列没有'X'
                if(a[i][k]-a[i][j-1]==0){
                    dp[i][j][k] = cal(1, k, j, m)%mod;
                    ans = (ans + dp[i][j][k]) % mod;
                }
            }
        }
        //计算前第i行二维前缀和
        sum(i);
    }

    cout<<(ans+mod)%mod<<endl;

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值