题解:CF1225E Rock Is Push

很玄妙的一道 dp 题。

Hint

Analysis

首先你要确保你会做当石头没有/固定的情况,这道题其实也是 dp。

考虑石头带来的影响,唯一的作用就是限制你的移动,比方说你下面有 \(3\) 块石头,由于只能向右或向下移动,你实际上往下只能走到当前列第 \(n-3\) 行。

于是对于石头的处理,设 \(rs[i][j]\)\(ds[i][j]\)\((i,j)\) 位置右/下方(包括 \((i,j)\))的石头数量,这两个数组可以用前缀和预处理出来。

我们将石头分成两个方向分别维护,于是也考虑分别设计两个方向的状态。

\(dpr[i][j]\)\(dpd[i][j]\) 为在 \((i,j)\) 位置,下一步向右/下走,最终走到 \((n,m)\) 方案数,初始状态 \(dpr[n][m]=dpd[n][m]=1\)

\(p.s.\) 这里为了方便代码,坐标采用第几行第几列的方式记录,与数学上不同

接着我们来推导 \(dpr[i][j]\) 的状态转移方程:

首先由于我们的初始状态是 \(dpr[n][m]\),所以我们实际上是倒着推的,所以下一步向右走实际上是前一步已经往左走了,于是我们转移所需要的状态在我们的右边,这就是这个状态设计的妙处。

还有,我们认为改变方向才是一次转移,即转移不是只走一格,而是在一条直线上走了一段,接着要改变方向了,才算是一次转移。所以 \(dpr[i][j]\) 应该由它右边的所有可以走到的结点转移来,而又因为我们要改变方向,所以我们应该取这些结点的 \(dpd\) 进行转移,很神奇吧。

最后考虑它右边哪些结点可以走到,这就要看石头了,显然右边有 \(rs[i][j+1]\) 个石头,所以最多走到第 \(m-rs[i][j+1]\) 列。

于是状态转移方程:

\[dpr[i][j]=\sum_{k=j+1}^{m-rs[i][j+1]}dpd[i][k] \]

同理推出 \(dpd[i][j]\) 的状态转移方程:

\[dpd[i][j]=\sum_{k=i+1}^{n-ds[i+1][j]}dpr[k][j] \]

于是两个状态转移方程就都出来了:

\[\begin{cases} dpr[i][j]=\sum_{k=j+1}^{m-rs[i][j+1]}dpd[i][k] \\\\ dpd[i][j]=\sum_{k=i+1}^{n-ds[i+1][j]}dpr[k][j] \end{cases}\]

接着用前缀和优化即可,时间复杂度 \(O(nm)\)

记得特判 \(n=1\)\(m=1\) 的情况哦。

Code

#include<bits/stdc++.h>
#define pb push_back
#define is insert
#define fi first
#define se second
#define bg begin
#define INF INT_MAX
#define mathmod(a,m) (((a)%(m)+(m))%(m))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int N=2005;
const ll MOD=1e9+7;
int n,m,rs[N][N],ds[N][N];
ll dpr[N][N],dpd[N][N],dprs[N][N],dpds[N][N];
char a[N][N];
char gc(){
    char c=getchar();
    while(c==' '||c=='\n'||c=='\r'||c=='\t') c=getchar();
    return c;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=gc();
    if(n==1&&m==1){printf("1");return 0;} // 特判
    // 前缀和预处理
    for(int i=n;i;i--){
        for(int j=m;j;j--){
            rs[i][j]=rs[i][j+1]+(a[i][j]=='R');
            ds[i][j]=ds[i+1][j]+(a[i][j]=='R');
        }
    }
    dpr[n][m]=dpd[n][m]=dprs[n][m]=dpds[n][m]=1;
    for(int i=n;i;i--){
        for(int j=m;j;j--){
            if(i==n&&j==m) continue; // 特判掉初始状态
            // 所有公式都有记得取模哦
            // dp
            dpr[i][j]=mathmod(dpds[i][j+1]-dpds[i][m-rs[i][j+1]+1],MOD);
            dpd[i][j]=mathmod(dprs[i+1][j]-dprs[n-ds[i+1][j]+1][j],MOD);
            // 前缀和优化
            dprs[i][j]=mathmod(dprs[i+1][j]+dpr[i][j],MOD);
            dpds[i][j]=mathmod(dpds[i][j+1]+dpd[i][j],MOD);
        }
    }
    printf("%lld",mathmod(dpr[1][1]+dpd[1][1],MOD));
    return 0;
}
posted @ 2024-10-23 14:00  godmoo  阅读(5)  评论(0编辑  收藏  举报