E. Rock Is Push
题意
出发点(1,1)到终点(n,m),只能向右和向下走,路上有障碍物用’R’表示,空地用’.'表示,你的力气巨大无比可以推动所有箱子,只要箱子不贴墙。求有多少种走法?
思路
棋盘dp 前缀和(优化计算降低复杂度)
DP题一般都会满足三个条件:子问题重叠、无后效性、最优子结构性质。
这道题满足以上三点。
dp[i][j]的方案数可由 dp[i][j−1]和 dp[i−1][j]推出,满足子问题重叠。
只用考虑最右边和最左边的箱子,其他箱子不用考虑,满足无后效性。
最优解由已知最优解推出满足最优子结构性质。
设二维状态数组 r,d,表示在(i,j)位置时下一步向右 (r)或向下 (d)走,到达目标位置(n,m)的方案总数,由定义可得 r[n][m]=d[n][m]=1
难点:
需要将向右走和向下走分成两个部分来分别dp
思考dp的时候得到状态转移方程之后自然而然想到要倒着dp(正难则反)
d数组代表可以下的方案数,r数组代表可以向右的方案数。
状态转移方程:
d[i][j]=∑k=1n−i−ds[i][j]r[i][j+k]
r[i][j]=∑k=1m−i−rs[i][j]d[i+k][j]
这里为了计算方便要前缀和算出这些和用 sumr[]sumd[]数组分别表示下一步往右走的前缀和 和下一步往下走的前缀和。
那么为什么d数组要对r数组求和呢?
首先因为我们下一步往下走,所以一定对下一行的数求解,那么为什么要对下一行往右走的数组求和呢?
假若我们对下一行往下走的数求和,你想想下一步往下的格子上一步一定在上面的格子。我们往下走一步,一定可以往右走。
同理r数组对d数组求和。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const double eps = 1e-8;
const int NINF = 0xc0c0c0c0;
const int INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
const ll maxn = 1e6 + 5;
const int N = 2050;
ll n,m,r[N][N],d[N][N],rs[N][N],ds[N][N],sumr[N][N],sumd[N][N];
char s[N];
int main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++){
if(s[j]=='R'){
rs[i][j]=1;//表示
ds[i][j]=1;
}
}
}
if(n==1&&m==1){cout<<1;return 0;}
//后缀和
for(int i=n;i>=1;i--){
for(int j=m;j>=1;j--){
rs[i][j]+=rs[i][j+1];
ds[i][j]+=ds[i+1][j];
}
}
r[n][m]=d[n][m]=sumr[n][m]=sumd[n][m]=1;
for(int i=n;i>=1;i--){
for(int j=m;j>=1;j--){
if(i==n&&j==m) continue;
r[i][j] = (sumd[i][j+1] - sumd[i][m - rs[i][j+1] + 1]) % mod;
d[i][j] = (sumr[i+1][j] - sumr[n - ds[i+1][j] + 1][j]) % mod;
sumr[i][j] = (sumr[i+1][j] + r[i][j]) % mod;//表示(i,j)到(n,m)范围内下一步右移的总个数
sumd[i][j] = (sumd[i][j+1] + d[i][j]) % mod;//表示(i,j)到(n,m)范围内下一步下移的总个数
}
}
cout<<((r[1][1] + d[1][1]) % mod + mod) % mod;//因为dp式中可能出现负数的情况故+mod再取模
return 0;
}