Rock Is Push【Codeforces 1247E】【DP + 前缀和DP】

Codeforces Round #596 (Div. 2, based on Technocup 2020 Elimination Round 2) E


You are at the top left cell (1,1)(1,1) of an ?×?n×m labyrinth. Your goal is to get to the bottom right cell (?,?)(n,m). You can only move right or down, one cell per step. Moving right from a cell (?,?)(x,y) takes you to the cell (?,?+1)(x,y+1), while moving down takes you to the cell (?+1,?)(x+1,y).

Some cells of the labyrinth contain rocks. When you move to a cell with rock, the rock is pushed to the next cell in the direction you're moving. If the next cell contains a rock, it gets pushed further, and so on.

The labyrinth is surrounded by impenetrable walls, thus any move that would put you or any rock outside of the labyrinth is illegal.

Count the number of different legal paths you can take from the start to the goal modulo 109+7109+7. Two paths are considered different if there is at least one cell that is visited in one path, but not visited in the other.

Input

The first line contains two integers ?,?n,m — dimensions of the labyrinth (1≤?,?≤20001≤n,m≤2000).

Next ?n lines describe the labyrinth. Each of these lines contains ?m characters. The ?j-th character of the ?i-th of these lines is equal to "R" if the cell (?,?)(i,j) contains a rock, or "." if the cell (?,?)(i,j) is empty.

It is guaranteed that the starting cell (1,1)(1,1) is empty.

Output

Print a single integer — the number of different legal paths from (1,1)(1,1) to (?,?)(n,m) modulo 109+7109+7.


  这道题,就是说有N*M的矩阵,里面放了很多个箱子R,我们现在想从(1,1)走到(N,M),问的是有几种走法?

  比赛时候的思路:记忆化搜索,我们从(1,1)出发,向(N,M)走,因为每个点都有一个向该方向上的剩余箱子量,我们可以记录方向以及剩余的箱子量,想到这个可能比较的稀疏,所以可以使用map来优化一下空间,但是最后的结果是并没有TLE,而且跑的挺快的,就是MLE了。

  后来的dp思路:这个也就是所谓的“2200分的dp”的正解了,确实不好想,赛后好久才能想到这个原因。我们现在可以假设一个dp[ i ][ j ][ op ],其中 “1 ≤ i,j ≤ 2000”,(i,j)表示点坐标,然后op只有0和1,0表示的是下一步是向右走,1表示的是下一步是向下走,我们从后往前推,也就是初始化dp[N][M][0] = dp[N][M][1] = 0;。然后通过最后的状态往前推回来

  我们现在看到(x,y)这个点,假如我们现在到了(x,y)这个点了,那么我们向右的概率应该是下一个点到一直(满足条件的,箱子都推到底了,或者人到底了)向左这一段中间的可能向下的方案数之和嘛。

简单的说,(我们举例下一步是向右了)dp[x][y][0] = \left ( dp[x][y+1][1] + dp[x][y+2][1] + \cdots +dp[x][k][1] \right )这里的K指的是最后都推到墙底了的(箱子推不下去了,或者人走到底了),但是这样的复杂度显然是O(N^{3})的,我们不难发现,可以用前缀(这里说实话是后缀的思想)来等效掉这里面的连续和的形式。

  则,我们可以记录一个前缀和(后缀和)pre[x][y][op]跟dp一个道理。但是可千万不要写错了pre[x][y][op],还是一样的举例向右的情况,它的前缀和的形式应该是:pre[x][y][0] = pre[x+1][y][0] + dp[x][y][0];。剩下的,就没什么了。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <unordered_map>
#include <unordered_set>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
const int mod = 1e9 + 7;
inline int MOOD(int &x) { while(x >= mod) x -= mod; return x; }
const int maxN = 2e3 + 5;
int N, M;
char mp[maxN][maxN];
int po[maxN][maxN][2];  //0右边,1下边
int dp[maxN][maxN][2] = {0};
int pre[maxN][maxN][2] = {0};   //前缀和
int main()
{
    scanf("%d%d", &N, &M);
    for(int i=1; i<=N; i++) scanf("%s", mp[i] + 1);
    for(int i=1; i<=N; i++)
    {
        po[i][M+1][0] = 0;
        for(int j=M; j>=1; j--)
        {
            po[i][j][0] = po[i][j+1][0];
            if(mp[i][j] == '.') po[i][j][0]++;
        }
    }
    for(int j=1; j<=M; j++)
    {
        po[N+1][j][1] = 0;
        for(int i=N; i>=1; i--)
        {
            po[i][j][1] = po[i+1][j][1];
            if(mp[i][j] == '.') po[i][j][1]++;
        }
    }
    if(mp[N][M] == 'R') { printf("0\n"); return 0; }
    if(N == 1 && M == 1) { printf("1\n"); return 0; }
    dp[N][M][0] = dp[N][M][1] = 1;
    pre[N][M][0] = pre[N][M][1] = 1;
    for(int i=N, det; i>=1; i--)
    {
        for(int j=M; j>=1; j--)
        {
            if(i == N && j == M) continue;
            det = po[i][j+1][0];
            dp[i][j][0] = pre[i][j+1][1] - pre[i][j + det + 1][1] + mod;
            MOOD(dp[i][j][0]);
            det = po[i+1][j][1];
            dp[i][j][1] = pre[i+1][j][0] - pre[i + det + 1][j][0] + mod;
            MOOD(dp[i][j][1]);
            pre[i][j][0] = pre[i+1][j][0] + dp[i][j][0];
            MOOD(pre[i][j][0]);
            pre[i][j][1] = pre[i][j+1][1] + dp[i][j][1];
            MOOD(pre[i][j][1]);
        }
    }
    int ans = dp[1][1][0] + dp[1][1][1];
    printf("%d\n", MOOD(ans));
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值