题目链接
题目描述
氧气少年给了 MoonLight 一个长度为 的排列p。
MoonLight 会不停地进行下面的操作,直到当前排列变成严格递增的排列:
- 选择一个最大的、不满足 的元素 ,然后将其向后移动到位置 ,其他元素保持原来的顺序不变。
上述过程可以用下面的伪代码表示:
现在,氧气少年不小心打乱了这个排列,每种排列出现的概率均等。
MoonLight 想知道,伪代码中 while 循环进行的轮数的期望值。
可以证明,答案可以表示成 的形式。其中,。
你只需输出 即可。
输入描述:
第一行包含一个整数 ,表示测试用例的组数。
对于每组测试用例:
仅输入一行,包含一个整数 ,表示排列 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。
接下来就是对高次幂和取模的处理,当然,高次幂一定是用快速幂来求解最合适。在这里,我们发现,每一步都依赖于前一步的结果,所以类型有点像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;
}