题目链接
https://ac.nowcoder.com/acm/contest/11261/K
题目大意
有一个
n
×
m
(
1
≤
n
,
m
≤
5
×
1
0
5
)
n \times m(1\leq n,m\leq 5\times 10^5)
n×m(1≤n,m≤5×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
≤
t
≤
5
×
1
0
5
)
t(1\leq t\leq 5\times 10^5)
t(1≤t≤5×105)。
题解
这道题要求算出合法路径数,每一步只会受到前一步的影响,所以我们用DP来转移方案数。
一般来说定义的DP式子定义为
d
p
i
,
j
dp_{i,j}
dpi,j表示在
(
i
,
j
)
(i,j)
(i,j)位置上的方案,显而易见,复杂度是
O
(
n
3
)
O(n^3)
O(n3)级别的,很不幸,无法实现,故我们想办法压缩维度。
由于它只能移动到相邻的点,所以我们可以更改DP的设定,将两个维度拆开处理。设
x
i
x_i
xi为纵向走
i
i
i 步不超出的方案数,
y
i
y_i
yi为横向走
i
i
i 步不超出的方案数,则原问题可变为求解
∑
i
=
0
t
x
i
∗
y
i
∗
C
i
t
\sum^{t}_{i=0}x_i*y_i*C_{i}^{t}
∑i=0txi∗yi∗Cit(
C
i
t
C_{i}^{t}
Cit表示从
t
t
t 步中选取
i
i
i 步)。
①显然可以用动态规划解决。
设
d
p
i
,
j
dp_{i,j}
dpi,j为走了
i
i
i 步,在
j
j
j 的方案数。
易得
d
p
0
,
j
=
0
dp_{0,j}=0
dp0,j=0 和
d
p
i
,
j
=
d
p
i
−
1
,
j
−
1
+
d
p
i
−
1
,
j
+
1
dp_{i,j}=dp_{i-1,j-1}+dp_{i-1,j+1}
dpi,j=dpi−1,j−1+dpi−1,j+1
它的复杂度为
O
(
n
t
)
O(nt)
O(nt),在
n
n
n 较小的时候较优,但在两者皆较大时时间会炸。
②我们尝试全局去考虑,对于每个坐标系
d
p
i
,
j
dp_{i,j}
dpi,j,它可以由
d
p
i
−
1
,
j
−
1
+
d
p
i
−
1
,
j
dp_{i-1,j-1}+dp_{i-1,j}
dpi−1,j−1+dpi−1,j转化过来,也就是有两种可能,而注意在边界时
d
p
i
,
1
=
d
p
i
−
1
,
2
dp_{i,1}=dp_{i-1,2}
dpi,1=dpi−1,2 和
d
p
i
,
n
=
d
p
i
−
1
,
n
−
1
dp_{i,n}=dp_{i-1,n-1}
dpi,n=dpi−1,n−1 ,只有一种可能,所以我们只需要考虑两个特殊值即可。
设
k
i
k_i
ki表示当前行\列的可能值。
易得
k
i
+
1
=
2
∗
k
i
−
d
p
i
,
1
−
d
p
i
,
n
k_{i+1}=2*k_i-dp_{i,1}-dp_{i,n}
ki+1=2∗ki−dpi,1−dpi,n
我们设
g
(
x
)
g(x)
g(x)为从
y
y
y走到
x
x
x的方案数。
如果不考虑走出边界的情况,则很容易通过排列组合得到
g
(
x
)
g(x)
g(x)的方案数。
但是若考虑这种情况,则需要减去不符合条件的部分,这里需要运用容斥的方法。
假如点
t
t
t 关于左边界
0
0
0 的对称点为
L
(
t
)
=
−
2
−
t
L(t)=-2-t
L(t)=−2−t ,关于右边界
n
+
1
n+1
n+1 的对称点为
R
(
t
)
=
2
∗
(
n
+
1
)
−
t
R(t)=2*(n+1)-t
R(t)=2∗(n+1)−t.
通过容斥我们可以得到
d
p
i
,
1
=
g
(
1
)
−
g
(
l
(
1
)
)
−
g
(
r
(
1
)
)
+
g
(
l
(
r
(
1
)
)
)
+
g
(
r
(
l
(
1
)
)
)
−
.
.
.
dp_{i,1}=g(1)-g(l(1))-g(r(1))+g(l(r(1)))+g(r(l(1)))-...
dpi,1=g(1)−g(l(1))−g(r(1))+g(l(r(1)))+g(r(l(1)))−...
由于每次操作都会使操作点
i
i
i 到
x
x
x 的距离减少
n
n
n ,所以该容斥式子的复杂度为
O
(
i
n
)
O(\frac{i}{n})
O(ni)。
这样做的复杂度为
O
(
T
2
n
)
O(\frac{T^2}{n})
O(nT2),对于
n
n
n 较大时方便实现。
总结:当 n n n 较小时选用动态规划①,当 n n n 较大时选用全局②。
参考代码
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define mk make_pair
#define , ,
#define ; ;
#define ( (
#define ) )
#define whlie while
#define viod void
#define mian main
#define read2(x,y) read(x);read(y);
#define FOR(i,n,m) for(int i=n;i<=m;i++)
#define For(i,n,m) for(int i=n;i>=m;i--)
using namespace std ;
void read(int &x) //快读
{
int ret=0;
char c=getchar(),last=' ' ;
whlie(!isdigit(c))
last=c,c=getchar();
while(isdigit(c))
ret=ret*10+c-'0',c=getchar();
if(last=='-')
x=-ret;
else
x=ret;
}
void read(ll &x)
{
ll ret=0;
char c=getchar(),last=' ';
whlie(!isdigit(c))
last=c,c=getchar();
while(isdigit(c))
ret=ret*10+c-'0',c=getchar();
if(last=='-')
x=-ret;
else
x=ret;
}
const int N=5e5+5,mod=998244353;
int n,t,m,a,b;
int fac[N],ifac[N],ans1[N],ans2[N];
int dp[2][705];
long long ans=0;
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;} //快速幂,求逆元
int C(int a,int b){return 1ll*fac[a]*ifac[b]%mod*ifac[a-b]%mod;}; //求组合数
void solve1(int s,int n,int a,int ans[])
{
int pos=0; //滚动数组
FOR(j,0,n+1)
dp[pos][j]=0;
dp[pos][a]=1;
ans[0]=1;
FOR(i,1,s)
{
pos=!pos;
FOR(j,0,n+1)
dp[pos][j]=0;
FOR(j,1,n)
{
dp[pos][j]=(dp[!pos][j-1]+dp[!pos][j+1])%mod; //动态转移
ans[i]=(ans[i]+dp[pos][j])%mod; //统计
}
}
}
int cal(int s,int n,int a,int pos)
{
int t1=pos,t2=pos,ans=0,q=abs(pos-a);
if(q<=s && (s-q)%2==0) //当距离为奇数时为非法情况,即不做统计
ans=C(s,(s+q)/2);
for(int i=1;;i++) //容斥,一加一减
{
if(i&1)
{
t1=-t1;
t2=2*(n+1)-t2;
}
else
{
t2=-t2;
t1=2*(n+1)-t1;
}
int d1=abs(t1-a),d2=abs(t2-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;
if(i&1)
ans=(ans-det+mod)%mod;
else
ans=(ans+det)%mod;
}
return ans;
}
void solve2(int s,int n,int a,int ans[])
{
ans[0]=1;
FOR(i,1,s)
ans[i]=(2ll*ans[i-1]+mod-cal(i-1,n,a,1)+mod-cal(i-1,n,a,n))%mod; //减去两种特殊情况
}
void solve(int s,int n,int a,int ans[])
{
if(1ll*n*n<s) //对于n较小时选用动态规划
solve1(s,n,a,ans);
else //当n较大时选用全局考虑
solve2(s,n,a,ans);
}
int main()
{
fac[0]=1;
FOR(i,1,N-1)
fac[i]=1ll*fac[i-1]*i%mod;
ifac[N-1]=powmod(fac[N-1],mod-2);
for(int i=N-1;i>=1;i--)
ifac[i-1]=1ll*ifac[i]*i%mod;
read2(t,n)
read2(m,a)
read(b);
solve(t,n,a,ans1);
solve(t,m,b,ans2);
FOR(i,0,t)
ans=(ans+1ll*C(t,i)*ans1[i]%mod*ans2[t-i]%mod)%mod;
printf("%lld\n",ans);
return 0;
}