Description
题目背景
冒泡排序的交换次数被定义为交换过程的执行次数。
题面描述
小 S 开始专注于研究⻓度为 n 的排列,他想知道,在你运气足够好的情况下(即每次冒泡排序的交换次数都是可能的最少交换次数,仿佛有上帝之手在操控),对于一个等概率随机的长度为n 的排列,进行这样的冒泡排序的期望交换次数是多少?
Input
从文件 inverse.in 中读入数据。
输入第一行包含一个正整数 T ,表示数据组数。
对于每组数据,第一行有一个正整数,保证 n ≤ 10^7 。
Output
输出到文件 inverse.out 中。
输出共 T 行,每行一个整数。
对于每组数据,输出一个整数,表示答案对 998244353 取模的结果。
Sample Input
2
2
4
样例 2
见选手目录下的 inverse/inverse2.in 与 inverse/inverse2.ans 。
Sample Output
499122177
415935149
Data Constraint
Solution
-
这题的方法非常多,各路大佬各显神通……
-
我还是从最简单的讲起吧:
方法①:
-
一个排列要变成 1 1 1 到 n n n,交换的方法肯定是沿着环来互换。
-
一个大小为 k k k 的环,就需要 k − 1 k-1 k−1 步交换来达到有序。
-
显然环越多所需的交换次数越少,若有 p p p 个环,则答案为 n − p n-p n−p 。
-
于是设 f [ i ] f[i] f[i] 表示这 i i i 个点形成环的期望个数。
-
则有转移: f [ i ] = f [ i − 1 ] + 1 i f[i]=f[i-1]+\frac{1}{i} f[i]=f[i−1]+i1
-
(第 i i i 个点可能自成一环,也可能被安排进前 i − 1 i-1 i−1 的那些环内)
-
于是我们 O ( n ) O(n) O(n) 预处理出 f [ i ] f[i] f[i] ,每读入一个 n n n 输出 n − f [ n ] n-f[n] n−f[n] 即可。
-
注意求 1 1 1 到 n n n 的倒数不要每次快速幂,会超时,应该 O ( n ) O(n) O(n) 线性求。
-
不会的话可以看看这里:O(N) 求 1~N 逆元 模板及证明
-
于是我们就 O ( n ) O(n) O(n) 解决本题啦!
方法②:
-
现在来讲 pty 提供的解法!
-
仍然考虑环的贡献,枚举环的大小 i i i ,单独算贡献,则答案即为: ∑ i = 2 n C n i ∗ ( i − 1 ) ! ∗ ( n − i ) ! ∗ ( i − 1 ) \sum_{i=2}^{n}C_{n}^{i}*(i-1)!*(n-i)!*(i-1) i=2∑nCni∗(i−1)!∗(n−i)!∗(i−1)
-
其中 C n i C_n^i Cni 表示从 n n n 中选 i i i 个点形成环, ( i − 1 ) ! (i-1)! (i−1)! 表示这 i i i 个点随便排(注意不是 i ! i! i! ,环的方向可以变), ( n − i ) ! (n-i)! (n−i)! 表示剩下 n − i n-i n−i 个点随便排, ( i − 1 ) (i-1) (i−1) 表示大小为 i i i 的环需要交换 ( i − 1 ) (i-1) (i−1) 次。
-
那么上式可化为: ∑ i = 2 n i − 1 i \sum_{i=2}^{n}\frac{i-1}{i} i=2∑nii−1
-
即: n − f [ n ] n-f[n] n−f[n] ,两种方法算出来的结果是一样的!
方法③:
-
当然还有一些对数字十分敏感的同学都能找到规律了。
-
设答案中分子为 a n a_n an ,则分母为 n ! n! n! ,可以发现关于 a n a_n an 的递推式: a n = n a n − 1 + ( n − 1 ) ( n − 1 ) ! a_n=na_{n-1}+(n-1)(n-1)! an=nan−1+(n−1)(n−1)!
-
发现方法玄学,这里不再展开,有兴趣的同学可以课下研究……
Code
#include<cstdio>
#include<cctype>
using namespace std;
typedef long long LL;
const int N=1e7+5,mo=998244353;
int f[N];
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
void write(int x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
int main()
{
freopen("inverse.in","r",stdin);
freopen("inverse.out","w",stdout);
f[0]=f[1]=1;
for(int i=2;i<N;i++) f[i]=(LL)(mo-mo/i)*f[mo%i]%mo;
for(int i=2;i<N;i++) f[i]=(f[i]+f[i-1])%mo;
int T=read();
while(T--)
{
int n=read();
write((n-f[n]+mo)%mo),putchar('\n');
}
return 0;
}