题意
河道里有 n n n个荷叶排成一排,从第 i ( < n ) i(<n) i(<n)个荷叶出发可以跳到第 ( i , i + a i ] (i,i+a_i] (i,i+ai]个荷叶上,有两只青蛙从第 i i i个荷叶出发,每一步都独立地等概率随机地跳向后边的荷叶,求两只青蛙以相同步数到达第 n n n个荷叶的概率。
n < = 8000 n <= 8000 n<=8000,保证 1 < = a i < = n − i 1 <= a_i <= n - i 1<=ai<=n−i
分析
这一题的容易联想到使用dp求解,并使用当前荷叶编号、已经跳跃的次数确定一个状态。
但在状态表示时,可能因为能够走到当前状态的次数作为状态表示,导致状态转移难以推算。
对于本题,最合适的做法是直接记录每个状态的概率。
即
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示从第1片荷叶跳
j
j
j次到达
i
i
i片荷叶的概率
根据分布乘法原理,易得状态转移方程:
f
[
i
+
x
]
[
j
+
1
]
+
=
f
[
i
]
[
j
]
/
a
i
,
(
x
<
=
a
i
)
f[i+x][j+1] += f[i][j] / a_i,(x <= a_i)
f[i+x][j+1]+=f[i][j]/ai,(x<=ai)
进一步,两只青蛙从第
1
1
1片荷叶跳跃
j
j
j次到达
i
i
i片荷叶的概率为:
f
[
i
]
[
j
]
∗
f
[
i
]
[
j
]
f[i][j] * f[i][j]
f[i][j]∗f[i][j],
再根据分类加法原理,两只青蛙以相同的步数到达第
n
n
n片荷叶的概率为:
∑
x
=
1
n
−
1
f
[
n
]
[
x
]
\sum_{x=1}^{n-1} f[n][x]
∑x=1n−1f[n][x]
注:青蛙一次跳跃至少前进一个荷叶,所以至多经过
n
−
1
n-1
n−1次跳跃到达第
n
n
n片荷叶。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10,MOD = 998244353;
int n,F[N][N],A[N],INV[N];
int qmi(int a,int b,int p){
int res = 1;
a %= p;
while(b){
if(b & 1) res = 1ll * res * a % p;
b >>= 1;
a = 1ll * a * a % p;
}
return res;
}
int main(){
scanf("%d",&n);
for(int i = 1;i < n;i++) scanf("%d",&A[i]), INV[i] = qmi(A[i],MOD-2,MOD);
F[1][0] = 1;
for(int i = 1;i < n;i++)
for(int j = 0;j <= n-1;j++)
for(int k = 1;k <= A[i] && i+k <= n;k++)
// F[i+k][j+1] += F[i][j] / A[i];
F[i+k][j+1] = (F[i+k][j+1] + 1ll * F[i][j] * INV[i]) % MOD;
int s = 0;
for(int i = 1;i <= n-1;i++)
s = (s + 1ll * F[n][i] * F[n][i]) % MOD;
printf("%d",s);
return 0;
}
O
(
n
3
)
O(n^3)
O(n3)的复杂度,TLE。
需要进行优化。
这里可以使用差分优化,将复杂度降为
O
(
n
2
)
O(n^2)
O(n2)。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10,MOD = 998244353;
int n,F[N][N],A[N],INV[N];
int qmi(int a,int b,int p){
int res = 1;
a %= p;
while(b){
if(b & 1) res = 1ll * res * a % p;
b >>= 1;
a = 1ll * a * a % p;
}
return res;
}
int main(){
scanf("%d",&n);
for(int i = 1;i < n;i++) scanf("%d",&A[i]), INV[i] = qmi(A[i],MOD-2,MOD);
F[1][0] = 1;
F[2][0] = -1;
for(int i = 1;i <= n;i++)
for(int j = 0;j <= n;j++){
F[i][j] = (F[i][j] + F[i-1][j]) % MOD;
int t = 1ll * F[i][j] * INV[i] % MOD;
F[i+1][j+1] = (1ll * F[i+1][j+1] + t) % MOD;
F[i+A[i]+1][j+1] = (1ll * F[i+A[i]+1][j+1] - t) % MOD;
}
int s = 0;
for(int i = 1;i <= n;i++)
s = (s + 1ll * F[n][i] * F[n][i]) % MOD;
printf("%d",s);
return 0;
}
这里需要注意,使用的差分为是一维的差分。
初始情况下,
f
[
1
]
[
0
]
f[1][0]
f[1][0]和
f
[
2
]
[
0
]
f[2][0]
f[2][0]为原
f
[
1
]
[
0
]
f[1][0]
f[1][0]和
f
[
2
]
[
0
]
f[2][0]
f[2][0]的差分值。
在计算过程中,数值可能超过int表示范围,通过类型转换方式解决。
将A数组的逆元进行预处理可减少计算量。