题目描述
小明对搭积木非常感兴趣。他的积木都是同样大小的正立方体。
在搭积木时,小明选取 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站讲解
下面是我看了这么多题解之后自己的理解:
- 首先,我们可以利用递推思想通过从第0层只有一种放置方案(第0层只能全放满),一步步递推到第n层计算出总共拥有多少种放置方案。我们定义数组dp[n][m][m],dp[i][j][k]表示第i行的j至k列必放积木时,总共有多少种放置方案。如:初始化dp[0][1][m]=1表示第0层1至m列只有一种摆放方式,即全部摆满。
- 上面讲了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;
}