2021牛客多校#10 K-Walking

原题链接

2021牛客多校训练赛#10 K-Walking

题目大意

有一个 n × m ( 1 ≤ n , m ≤ 5 × 1 0 5 ) n\times m(1\le n,m\le 5\times 10^5) n×m(1n,m5×105)大小的迷宫,两角坐标为 ( 1 , 1 ) (1,1) (1,1) ( n , m ) (n,m) (n,m)
若你当前在 ( x , y ) (x,y) (x,y)位置,美妙你可以向上下左右四个方向移动一格,不能停留在原地,不能移动到迷宫外。

初始在 ( a , b ) (a,b) (a,b)处,继续移动 t ( 1 ≤ n ≤ 5 × 1 0 5 ) t(1\le n\le 5\times 10^5) t(1n5×105)秒,求有多少种合法的移动路径。

题解

求路径方案数,我们常用DP推出动态转移式来计算,但本题如果我们直接计算所有方案的话,算法复杂度会很高,并且不容易实现。不妨换种思路,我们把所有路径拆成不同的情况,即上下移动、左右移动操作并进行分别考虑。
假设上下移动的方案为 X i ( 1 ≤ i ≤ T ) X_i(1\le i\le T) Xi(1iT),左右移动为 Y j ( 1 ≤ j ≤ T ) Y_j(1\le j\le T) Yj(1jT) i 、 j i、j ij分别为操作步数)。
则我们可以得到答案 a n s = ∑ i = 0 T X i × Y T − i × C T i ans=\sum^{T}_{i=0}X_i\times Y_{T-i}\times C^{i}_{T} ans=i=0TXi×YTi×CTi C C C为上下方向和左右方向的所有组合, i = 0 i=0 i=0时即为在上下或左右方向上不动的情况,此时 X 1 = Y 1 = 1 X_1=Y_1=1 X1=Y1=1

然后就是对 X X X以及 Y Y Y的计算。

方案一:首先,我们假设上下(或左右)方向移动方案为 d p t , x dp_{t,x} dpt,x t t t为移动步数, x x x表示移动至第几行或第几列即状态)。
然后我们可以发现,在同一方向(即上下或左右)上的某一个位置有两个点可以到达此点(除去边界只有一个点可以到达,但是在实现代码时并不影响计算值),所以我们可以得到转移式为:
d p t , x = d p t − 1 , x − 1 + d p t − 1 , x + 1 dp_{t,x}=dp_{t-1,x-1}+dp_{t-1,x+1} dpt,x=dpt1,x1+dpt1,x+1
但是这种方案的时间复杂的为 O ( T n ) O(Tn) O(Tn),在数据非常大的时候会TLE,我们再想想别的解决方案。

假设 K ( i ) K(i) K(i)为第 i i i步的方案数。
我们先将 d p t , x dp_{t,x} dpt,x分解成 d p t − 1 , x − 1 + d p t − 1 , x + 1 dp_{t-1,x-1}+dp_{t-1,x+1} dpt1,x1+dpt1,x+1,不难发现,每个 d p t , x dp_{t,x} dpt,x对方案的贡献系数为2(除去边界的两个点的贡献系数为1)。然后我们可以得到 K ( i ) = 2 × K ( i − 1 ) − d p i − 1 , 1 − d p i − 1 , n K(i)=2\times K(i-1)-dp_{i-1,1}-dp_{i-1,n} K(i)=2×K(i1)dpi1,1dpi1,n,所以我们只需要考虑两个特殊值即可。
以计算 d p i − 1 , 1 dp_{i-1,1} dpi1,1为例( d p i − 1 , n dp_{i-1,n} dpi1,n同理)
如果可以无限制的跑,我们假设从 x x x i i i步到 y y y的方案为 P ( y ) P(y) P(y),是一个组合数值。
但如果要算出 d p t − 1 , 1 dp_{t-1,1} dpt1,1的值我们就需要在 P ( 1 ) P(1) P(1)的基础上减去不合法情况。
假设点 w w w以左边界点 0 0 0为对称轴的位置为 L ( w ) = − 2 w L(w)=-2w L(w)=2w,以右边界点 n + 1 n+1 n+1为对称轴的位置为 R ( w ) = 2 × ( n + 1 ) − w R(w)=2\times (n+1)-w R(w)=2×(n+1)w
通过容斥考虑重复计算的方案,可以得到:
d p t − 1 , 1 = P ( 1 ) − P ( L ( 1 ) ) − P ( R ( 1 ) ) + P ( L ( R ( 1 ) ) ) + P ( R ( L ( 1 ) ) ) − . . . dp_{t-1,1}=P(1)-P(L(1))-P(R(1))+P(L(R(1)))+P(R(L(1)))-... dpt1,1=P(1)P(L(1))P(R(1))+P(L(R(1)))+P(R(L(1)))...
因为每次对称移动的距离至少为 n n n(或 m m m),所以此时每种方案的时间复杂度为 O ( i n ) O(\frac{i}{n}) O(ni),总时间复杂度为 O ( T 2 n ) O(\frac{T^2}{n}) O(nT2)

通过对比两种解决方案的复杂度,我们分情况进行采用。可以发现,当 n n n(或 m m m) = T =\sqrt{T} =T 时,两种方案的复杂度均为 O ( T T ) O(T\sqrt{T}) O(TT ),所以我们以 n n n(或 m m m) = T =\sqrt{T} =T 为分界线,当 n n n(或 m m m) < T <\sqrt{T} <T 时, O ( T n ) O(Tn) O(Tn)的复杂度更低,反之, O ( T 2 n ) O(\frac{T^2}{n}) O(nT2)的复杂度更低。

参考代码

#include<bits/stdc++.h>
#define mod 998244353
using namespace std;
const int N=5e5+5;
const int M=7e2+10;
int frac[N],invf[N],X[N],Y[N];
int C(int n,int m){return 1ll*frac[n]*invf[m]%mod*invf[n-m]%mod;}
int powmod(int a,int b){int ret=1;while(b){if(b&1)ret=1ll*ret*a%mod;a=1ll*a*a%mod;b>>=1;}return ret;}
void init()
{
	frac[0]=1;
	for(int i=1;i<N;i++)frac[i]=1ll*frac[i-1]*i%mod;
	invf[N-1]=powmod(frac[N-1],mod-2);
	for(int i=N-1;i;i--)
	invf[i-1]=1ll*invf[i]*i%mod;
}
int cal(int s,int n,int a,int pos)
{
	int pos1=pos,pos2=pos,ans=0,d=abs(pos-a);
	if(d<=s&&(s-d)%2==0)ans=C(s,(s+d)/2);
	for(int j=1;;j++)
	{
		if(j&1){pos1=-pos1;pos2=2*(n+1)-pos2;}
		else{pos1=2*(n+1)-pos1;pos2=-pos2;}
		int d1=abs(pos1-a),d2=abs(pos2-a),det=0;
		if(d1>s&&d2>s)break;
		if(d1<=s&&(s-d1)%2==0)det=(det+C(s,(s+d1)/2))%mod;
		if(d2<=s&&(s-d2)%2==0)det=(det+C(s,(s+d2)/2))%mod;//如果s-d2不是偶数,即不可能走到那个节点,为非法情况
		if(j&1)ans=(ans+mod-det)%mod;
		else ans=(ans+det)%mod;
	}
	return ans;
}
int dp[2][N];
int n,m,x,y,t;
void w()
{
	if(1ll*n*n<=t)
	{
		dp[0][x]=1;X[0]=1;
		for(int i=1;i<=t;i++)
		{
			dp[1][1]=dp[0][2];X[i]=(X[i]+dp[1][1])%mod;
			dp[1][n]=dp[0][n-1];X[i]=(X[i]+dp[1][n])%mod;
			for(int j=2;j<n;j++)
			{
				dp[1][j]=(dp[0][j-1]+dp[0][j+1])%mod;
				X[i]=(X[i]+dp[1][j])%mod;
				dp[0][j-1]=dp[1][j-1];
			}
			dp[0][n-1]=dp[1][n-1];
			dp[0][n]=dp[1][n];
		}
		for(int j=1;j<=n;j++)
		dp[0][j]=dp[1][j]=0;
	}
	if(1ll*m*m<=t)
	{
		dp[0][y]=1;Y[0]=1;
		for(int i=1;i<=t;i++)
		{
			dp[1][1]=dp[0][2];Y[i]=(Y[i]+dp[1][1])%mod;
			dp[1][m]=dp[0][m-1];Y[i]=(Y[i]+dp[1][m])%mod;
			for(int j=2;j<m;j++)
			{
				dp[1][j]=(dp[0][j-1]+dp[0][j+1])%mod;
				Y[i]=(Y[i]+dp[1][j])%mod;
				dp[0][j-1]=dp[1][j-1];
			}
			dp[0][m-1]=dp[1][m-1];
			dp[0][m]=dp[1][m];
		}
		for(int j=1;j<=m;j++)
		dp[0][j]=dp[1][j]=0;
	}
	//分情况讨论,分界值即为根号T
	if(1ll*n*n>t)
	{
		X[0]=1;
		for(int i=1;i<=t;i++)
			X[i]=(2ll*X[i-1]+mod-cal(i-1,n,x,1)+mod-cal(i-1,n,x,n))%mod;
	}
	if(1ll*m*m>t)
	{
		Y[0]=1;
		for(int i=1;i<=t;i++)
			Y[i]=(2ll*Y[i-1]+mod-cal(i-1,m,y,1)+mod-cal(i-1,m,y,m))%mod;
	}
}
int main()
{
	init();
	scanf("%d%d%d%d%d",&t,&n,&m,&x,&y);
	w();
	int ans=0;
	for(int i=0;i<=t;i++)
		ans=(ans+1ll*C(t,i)*X[i]%mod*Y[t-i]%mod)%mod;
	printf("%d",ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值