JZOJ 5931. 【NOIP2018模拟10.27】冒泡排序

12 篇文章 0 订阅
11 篇文章 0 订阅

Description

题目背景
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
Sample Output

Data Constraint

Data Constraint

Solution

  • 这题的方法非常多,各路大佬各显神通……

  • 我还是从最简单的讲起吧:

方法①:

  • 一个排列要变成 1 1 1 n n n,交换的方法肯定是沿着环来互换。

  • 一个大小为 k k k 的环,就需要 k − 1 k-1 k1 步交换来达到有序。

  • 显然环越多所需的交换次数越少,若有 p p p 个环,则答案为 n − p n-p np

  • 于是设 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[i1]+i1

  • (第 i i i 个点可能自成一环,也可能被安排进前 i − 1 i-1 i1 的那些环内)

  • 于是我们 O ( n ) O(n) O(n) 预处理出 f [ i ] f[i] f[i] ,每读入一个 n n n 输出 n − f [ n ] n-f[n] nf[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=2nCni(i1)!(ni)!(i1)

  • 其中 C n i C_n^i Cni 表示从 n n n 中选 i i i 个点形成环, ( i − 1 ) ! (i-1)! (i1)! 表示这 i i i 个点随便排(注意不是 i ! i! i! ,环的方向可以变), ( n − i ) ! (n-i)! (ni)! 表示剩下 n − i n-i ni 个点随便排, ( i − 1 ) (i-1) (i1) 表示大小为 i i i 的环需要交换 ( i − 1 ) (i-1) (i1) 次。

  • 那么上式可化为: ∑ i = 2 n i − 1 i \sum_{i=2}^{n}\frac{i-1}{i} i=2nii1

  • 即: n − f [ n ] n-f[n] nf[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=nan1+(n1)(n1)!

  • 发现方法玄学,这里不再展开,有兴趣的同学可以课下研究……

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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值