![[COGS 2066]七十和十七 [COGS 2066]七十和十七](https://image.shishitao.com:8440/aHR0cHM6Ly9ia3FzaW1nLmlrYWZhbi5jb20vdXBsb2FkL2NoYXRncHQtcy5wbmc%2FIQ%3D%3D.png?!?w=700&webp=1)
2066. 七十和十七
★★★ 输入文件:
xvii.in
输出文件:xvii.out
简单对比
时间限制:1 s 内存限制:256 MB【题目描述】
七十君最近爱上了排序算法,于是Ta让十七君给Ta讲冒泡排序。
十七君给七十君讲完了冒泡排序以后,七十君回家苦思冥想,又创造了一种名
为七十排序的算法。下面是这个算法排序一个排列的过程:
首先从左到右扫描每个相邻数对。如果这两个数是逆序的,则将第二个数(也
就是小的数)放在整个排列的开头,其他数位置不变,并把计数器加一。如果
没有逆序的相邻数对了,就说明已经排好序了,算法终止。
七十君认为计数器的值反映了这个算法的运行时间。但十七君觉得七十君发明
的这个算法会很慢,所以他请你帮忙算算,对于所有长度为n的排列P,
\[E(n)=\frac{\sum f(P)}{n!}\]
的值,这里f(P)表示排列P运行算法结束时计数器的值。
【输入格式】
一行一个整数n。
【输出格式】
如果E(n)=a/b,求c使得
bc 三 a (mod 10^9+7)
并输出,其中0≤c<10^9+7,如果e不存在输出-1。
【样例输入】
4【样例输出】
250000005【提示】
对于排列4 1 3 2,算法结束时计数器的值为5。
4 1 3 2,4和1形成逆序,将1放到排列最前方。
1 4 3 2,4和3形成逆序,将3放到排列最前方。
3 1 4 2,3和1形成逆序,将1放到排列最前方。
1 3 4 2,4和2形成逆序,将2放到排列最前方。
2 1 3 4,2和1形成逆序,将1放到排列最前方。
1 2 3 4,现在排列已经排序完毕。
E(4)=3.25。
数据范围与约定
对于20%的数据,n≤8。
对于40%的数据,n≤30。
对于60%的数据,n≤200。
对于1OO%的数据,n≤10^5。
题解
首先我们可以发现, 将 $n$ 个数排序的过程可以转化为按方案排序 $n-1$ 个数后将最后一个数按方案再排进去. 对于长度为 $n$ 的全排列, 若第 $n$ 个数 $a_n=n$ , 则不会引起计数器变动(因为它在前 $n-1$ 个排好序后就已经在最后了), 否则会引起计数器增加 $2^{a_n-1}$ . 枚举最后加入的数 $a_n$ 即可在 $O(n^2)$ 时间复杂度内解决. 最终表达式为:
\[ans=\sum_{i=1}^n\sum_{j=1}^{i-1}2^{j-1}\]
注意到第二部分求和为等差数列形式, 我们可以通过等差数列求和公式进行计算. 于是上式可以化简为:
\[ans=\sum_{i=1}^n\frac{2^{i-1}-1}{i}\]
参考代码
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm> const int MOD=1e9+; int Pow(int,int,int); int main(){
int n;
scanf("%d",&n);
int ans=;
for(int i=;i<=n;i++){
ans=(ans+1ll*(Pow(,i-,MOD)-+MOD)%MOD*Pow(i,MOD-,MOD)%MOD)%MOD;
}
printf("%d\n",ans);
return ;
} int Pow(int a,int n,int p){
int ans=;
while(n>){
if((n&)!=){
ans=1ll*ans*a%p;
}
a=1ll*a*a%p;
n>>=;
}
return ans;
}
Backup