G - MoonLight的冒泡排序难题

113 阅读2分钟

题目链接

题目描述

氧气少年给了 MoonLight 一个长度为 n(1n2105)n(1≤n≤2⋅10^5)的排列p。

MoonLight 会不停地进行下面的操作,直到当前排列变成严格递增的排列:

  • 选择一个最大的、不满足 pi=ip_i=i 的元素 pip_i,然后将其向后移动到位置 pip_i,其他元素保持原来的顺序不变。

上述过程可以用下面的伪代码表示:

image.png 现在,氧气少年不小心打乱了这个排列,每种排列出现的概率均等。

MoonLight 想知道,伪代码中 while 循环进行的轮数的期望值。

可以证明,答案可以表示成 p/qp/q 的形式。其中,p0,q1,gcd(p,q)=1,qmod  9982443530p≥0,q≥1,gcd⁡(p,q)=1,q mod  998244353≠0

你只需输出 pq998244351mod  998244353p⋅q^{998244351} mod  998244353 即可。

输入描述:

第一行包含一个整数 T(1T2105)T(1≤T≤2⋅10^5),表示测试用例的组数。

对于每组测试用例:

仅输入一行,包含一个整数 n(1n2105)n(1≤n≤2⋅10^5),表示排列 p 的长度。

输出描述:

对于每组测试用例:

仅输出一行,包含一个整数,表示答案。

输入

3
1
2 
3

输出

0
499122177
166374060

解题思路

这道题其实是一道推公式的数学题。

这里求的是while循环的轮数的期望值,这里我们找排列可以用如下图的插空的方式,对于2个数,有两种可能(12,21)期望为1/2,3个数(只要3不在末位,就要循环把它放到末位),期望为3/2,4个数期望为3/4。以此类推,i个数,期望为i-1/i

image-20230529193306445.png

接下来就是对高次幂和取模的处理,当然,高次幂一定是用快速幂来求解最合适。在这里,我们发现,每一步都依赖于前一步的结果,所以类型有点像dp。
主要代码如下:

   f[0]=0;
   for (int i = 1; i <= 2e5; i++)
   {
       f[i] = (f[i - 1] + (i - 1) * qmi(i, mod - 2) % mod) % mod;
   }

AC代码

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    using namespace std;
    #define int long long
    #define endl '\n'
    #define pair PII;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef long double ld;
    const int mod = 998244353;
    const int N = 200010;
    int f[N];
    int qmi(int a, int k)
    {
        int res = 1 ;
        while (k)
        {
            if (k & 1) res = res * a % mod;
            k >>= 1;
            a = a * a % mod;
        }
        return res;
    }
    ​
    signed main()
    {
        ios_base::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
        f[0]=0;
        for (int i = 1; i <= 2e5; i++)
        {
            f[i] = (f[i - 1] + (i - 1) * qmi(i, mod - 2) % mod) % mod;
        }
        int n;
        cin >> n;
        while (n--)
        {
            int q;
            cin >> q;
            cout << f[q]<<endl;
        }
    ​
        return 0;
    ​
    }