E. Rock Is Push
題意
出發點(1,1)到終點(n,m),只能向右和向下走,路上有障礙物用’R’表示,空地用’.'表示,你的力氣巨大無比可以推動所有箱子,只要箱子不貼牆。求有多少種走法?
思路
棋盤dp 前綴和(優化計算降低複雜度)
DP題一般都會滿足三個條件:子問題重疊、無後效性、最優子結構性質。
這道題滿足以上三點。
的方案數可由和推出,滿足子問題重疊。
只用考慮最右邊和最左邊的箱子,其他箱子不用考慮,滿足無後效性。
最優解由已知最優解推出滿足最優子結構性質。
設二維狀態數組 ,表示在(i,j)位置時下一步向右或向下走,到達目標位置(n,m)的方案總數,由定義可得
難點:
需要將向右走和向下走分成兩個部分來分別dp
思考dp的時候得到狀態轉移方程之後自然而然想到要倒着dp(正難則反)
d數組代表可以下的方案數,r數組代表可以向右的方案數。
狀態轉移方程:
這裏爲了計算方便要前綴和算出這些和用數組分別表示下一步往右走的前綴和 和下一步往下走的前綴和。
那麼爲什麼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;
}