CF1225E

2 篇文章 0 订阅
1 篇文章 0 订阅

E. Rock Is Push

题意

出发点(1,1)到终点(n,m),只能向右和向下走,路上有障碍物用’R’表示,空地用’.'表示,你的力气巨大无比可以推动所有箱子,只要箱子不贴墙。求有多少种走法?

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

思路

棋盘dp 前缀和(优化计算降低复杂度)
DP题一般都会满足三个条件:子问题重叠、无后效性、最优子结构性质。
这道题满足以上三点。
d p [ i ] [ j ] dp[i][j] dp[i][j]的方案数可由 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1] d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]推出,满足子问题重叠。
只用考虑最右边和最左边的箱子,其他箱子不用考虑,满足无后效性。
最优解由已知最优解推出满足最优子结构性质。
设二维状态数组 r , d r,d r,d,表示在(i,j)位置时下一步向右 ( r ) (r) r或向下 ( d ) (d) d走,到达目标位置(n,m)的方案总数,由定义可得 r [ n ] [ m ] = d [ n ] [ m ] = 1 r[n][m]=d[n][m]=1 r[n][m]=d[n][m]=1

难点:
需要将向右走和向下走分成两个部分来分别dp
思考dp的时候得到状态转移方程之后自然而然想到要倒着dp(正难则反)

d数组代表可以下的方案数,r数组代表可以向右的方案数。
状态转移方程:
d [ i ] [ j ] = ∑ k = 1 n − i − d s [ i ] [ j ] r [ i ] [ j + k ] d[i][j]=\sum_{k=1}^{n-i-ds[i][j]}r[i][j+k] d[i][j]=k=1nids[i][j]r[i][j+k]
r [ i ] [ j ] = ∑ k = 1 m − i − r s [ i ] [ j ] d [ i + k ] [ j ] r[i][j]=\sum_{k=1}^{m-i-rs[i][j]}d[i+k][j] r[i][j]=k=1mirs[i][j]d[i+k][j]
这里为了计算方便要前缀和算出这些和用 s u m r [ ] s u m d [ ] sumr[] sumd[] 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值