[蔚来杯]Two Frogs

题目链接

题意

河道里有 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<=ni

分析

这一题的容易联想到使用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=1n1f[n][x]
注:青蛙一次跳跃至少前进一个荷叶,所以至多经过 n − 1 n-1 n1次跳跃到达第 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数组的逆元进行预处理可减少计算量。

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...
win11本地账户怎么改名?win11很多操作都变了样,用户如果想要...