(dp)CF1225E. Rock Is Push

CF1225E. Rock Is Push

题意:

你在一个 n × m n\times m n×m矩阵中的 ( 1 , 1 ) (1,1) (1,1)点,要到达 ( n , m ) (n,m) (n,m)点,你只能向右或者向下走。矩阵中有些格子有石头,你走到一个有石头的点可以按照你的移动方向推动石头,如果那个点上也有一个石头,它就会被按相同方向推的更远,以此类推。但是石头不能被推出矩阵外。问有多少种到达方案,对 1 0 9 + 7 10^9+7 109+7取模。

思路:

非常巧妙的一道dp题。不看题解真的想不到。
如果没有障碍物,或者障碍物不能移动,那么是一道比较简单的问题。
我们可以考虑到,只有下方或者右方的石头会对我们的路线产生影响。
并且因为石头不能被推出墙外,所以我们可以得到,假设我在 ( i , j ) (i,j) (i,j)这一点,如果右边有 k k k个石头,而我想一直往右走的话,最多能走 m − j − k m-j-k mjk步,也就是最远走到 ( i , m − k ) (i,m-k) (i,mk)点。我们用 r [ i ] [ j ] r[i][j] r[i][j] d [ i ] [ j ] d[i][j] d[i][j]表示 ( i , j ) (i,j) (i,j)点的右边和下边有多少个石头。
我们将向右和向下的步骤拆开,用 d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] dp[i][j][0/1]表示在 ( i , j ) (i,j) (i,j)这一点时,向右/下走,并且与前一步行走方式不同的方案数。我们可以假设我们在 ( i , j ) (i,j) (i,j)点,这个时候, d p [ i ] [ j ] [ 0 ] dp[i][j][0] dp[i][j][0]表示向右走的方案,我们可以一直向右走,直到走不了,在期间可以选择转向(向下走)。所以它的值取决于在 ( i , j + 1 ) (i,j+1) (i,j+1) ( i , m − r [ i ] [ j ] ) (i,m-r[i][j]) (i,mr[i][j])哪一点改变行走方向。
即,我们可以得到式子:
d p [ i ] [ j ] [ 0 ] = ∑ k = 1 m − j − r [ i ] [ j ] d p [ i ] [ j + k ] [ 1 ] dp[i][j][0]=\sum_{k=1}^{m-j-r[i][j]}dp[i][j+k][1] dp[i][j][0]=k=1mjr[i][j]dp[i][j+k][1]
d p [ i ] [ j ] [ 1 ] = ∑ k = 1 n − i − d [ i ] [ j ] d p [ i + k ] [ j ] [ 0 ] dp[i][j][1]=\sum_{k=1}^{n-i-d[i][j]}dp[i+k][j][0] dp[i][j][1]=k=1nid[i][j]dp[i+k][j][0]
这样我们可以从 ( n , m ) (n,m) (n,m)点进行逆推,设 d p [ n ] [ m ] [ 0 ] = d p [ n ] [ m ] [ 1 ] = 1 dp[n][m][0]=dp[n][m][1]=1 dp[n][m][0]=dp[n][m][1]=1,最后答案为 d p [ 1 ] [ 1 ] [ 0 ] + d p [ 1 ] [ 1 ] [ 1 ] dp[1][1][0]+dp[1][1][1] dp[1][1][0]+dp[1][1][1]
对于上面的状态转移方程,每次暴力算显然是过于耗费时间,可以计算一个后缀和 s u m r [ i ] [ j ] sumr[i][j] sumr[i][j] s u m d [ i ] [ j ] sumd[i][j] sumd[i][j]。这样可以转换为:
d p [ i ] [ j ] [ 0 ] = s u m d [ i ] [ j + 1 ] − s u m d [ i ] [ m − r [ i ] [ j + 1 ] + 1 ] dp[i][j][0]=sumd[i][j+1]-sumd[i][m-r[i][j+1]+1] dp[i][j][0]=sumd[i][j+1]sumd[i][mr[i][j+1]+1]
d p [ i ] [ j ] [ 1 ] = s u m r [ i + 1 ] [ j ] − s u m r [ n − d [ i + 1 ] [ j ] + 1 ] [ j ] dp[i][j][1]=sumr[i+1][j]-sumr[n-d[i+1][j]+1][j] dp[i][j][1]=sumr[i+1][j]sumr[nd[i+1][j]+1][j]
s u m r [ i ] [ j ] = s u m r [ i + 1 ] [ j ] + d p [ i ] [ j ] [ 0 ] sumr[i][j]=sumr[i+1][j]+dp[i][j][0] sumr[i][j]=sumr[i+1][j]+dp[i][j][0]
s u m d [ i ] [ j ] = s u m d [ i ] [ j + 1 ] + d p [ i ] [ j ] [ 1 ] sumd[i][j]=sumd[i][j+1]+dp[i][j][1] sumd[i][j]=sumd[i][j+1]+dp[i][j][1]
注意特判 1 × 1 1\times1 1×1的情况。

代码:

#include<bits/stdc++.h>
#define pii pair<int,int>
#define int long long
#define cl(x,y) memset(x,y,sizeof(x))
#define ct cerr<<"Time elapsed:"<<1.0*clock()/CLOCKS_PER_SEC<<"s.\n";
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define all(x) x.begin(),x.end()
#define lson x<<1,l,mid
#define rson x<<1|1,mid+1,r
#define INF 1e18
const int N=2e3+10;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const double eps=1e-8;
const double pi=acos(-1);
using namespace std;
char a[N][N];
int sumd[N][N]={0},sumr[N][N]={0},dp[N][N][2]={0},d[N][N]={0},r[N][N]={0};
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n,m,i,j;
	cin>>n>>m;
	for(i=1;i<=n;i++)
	{
		cin>>a[i]+1;
		for(j=1;j<=m;j++)
			if(a[i][j]=='R')
				d[i][j]=r[i][j]=1; 
	}
	for(i=n;i>=1;i--)
		for(j=m;j>=1;j--)
		{
			d[i][j]+=d[i+1][j];
			r[i][j]+=r[i][j+1];
		}
	dp[n][m][0]=dp[n][m][1]=sumd[n][m]=sumr[n][m]=1;
	if(n==1 && m==1)
	{
		cout<<1<<endl;
		return 0;
	}
	for(i=n;i>=1;i--)
		for(j=m;j>=1;j--)
		{
			if(i==n && j==m)
				continue;
			dp[i][j][0]=(sumd[i][j+1]-sumd[i][m-r[i][j+1]+1]+mod)%mod;
			dp[i][j][1]=(sumr[i+1][j]-sumr[n-d[i+1][j]+1][j]+mod)%mod;
			sumr[i][j]=(sumr[i+1][j]+dp[i][j][0])%mod;
			sumd[i][j]=(sumd[i][j+1]+dp[i][j][1])%mod;						
		}
	cout<<(dp[1][1][0]+dp[1][1][1])%mod<<endl;
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值