【CF1081G】Mergesort Strikes Back(期望)

本文介绍了一种计算逆序对数量的方法,通过将序列分块并利用调和级数前缀和来快速计算逆序对的概率,最终达到O(n)的时间复杂度。文章详细解释了算法思路,包括如何处理块内和跨块的逆序对贡献,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门


不知道为什么一堆写了 O ( n ) O(n) O(n)做法的声称自己复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)

题解

首先我们先把该分的块分了,也就是分到深度不超过 k k k或者分到叶子。

块内是没有办法排序的,我们知道一个长度为 k k k的随机序列的期望逆序对个数为 k ( k − 1 ) 4 \frac{k(k-1)}{4} 4k(k1)可以直接算。

然后现在需要考虑两个块之间的贡献。

显然我们将每个块按照前缀最大值分成若干块,显然这个归并排序的本质是在对块头进行排序。

考虑两个块,第一个块的第 i i i个元素和第二个块的第 j j j个元素。考虑它们贡献一个逆序对的概率就是 i i i排到 j j j前面且 i i i j j j大,或者 j j j排到 i i i前且 i i i j j j大。

它们谁在谁前面是完全依靠它们各自的块头决定的,但是仔细想一想,它们肯定无法贡献逆序对的情况只有它们其中一个是这 i + j i+j i+j个数中的最大值的情况,剩下的所有情况它们排序的先后与它们自己的大小无关,并且都有 1 2 \frac{1}{2} 21的概率贡献逆序对。即概率为 i + j − 2 2 ( i + j ) = 1 2 − 1 i + j \frac{i+j-2}{2(i+j)}=\frac{1}{2}-\frac{1}{i+j} 2(i+j)i+j2=21i+j1

维护一下调和级数的前缀和就可以快速计算了。

由于不同siz的叶子最多只有两种,所以复杂度实际上是 O ( 1 ) × O ( n ) = O ( n ) O(1)\times O(n)=O(n) O(1)×O(n)=O(n)


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;
#define fi first
#define se second

int mod;
inline int add(int a,int b){a+=b-mod;return a+(a>>31&mod);}
inline int dec(int a,int b){a-=b;return a+(a>>31&mod);}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}

cs int N=1e5+7;

int n,k;
int inv[N],H[N];
std::map<int,int> cnt;

inline void solve(int l,int r,int d){
	if(d==1||l==r){cnt[r-l+1]++;return ;}
	int mid=l+r>>1;
	solve(l,mid,d-1);solve(mid+1,r,d-1);
}

inline int calc(int x,int y){
	int ans=mul(x,y);ans=mul(ans,mod+1>>1);
	for(int re i=1;i<=x;++i)Inc(ans,dec(H[i],H[i+y]));
	return ans;
}

signed main(){
	scanf("%d%d%d",&n,&k,&mod);
	int iv2=mod+1>>1,iv4=mul(iv2,iv2);
	inv[0]=inv[1]=H[0]=H[1]=1;
	for(int re i=2;i<=n;++i)H[i]=add(H[i-1],inv[i]=mul(mod-mod/i,inv[mod%i]));
	solve(1,n,k);int ans=0;
	for(auto t:cnt){
		Inc(ans,mul(mul(t.fi,t.fi-1),mul(iv4,t.se)));
		Inc(ans,mul(mul(t.se,t.se-1),mul(iv2,calc(t.fi,t.fi))));
	}
	for(auto i1:cnt)
	for(auto i2:cnt)if(i1.fi<i2.fi){
		Inc(ans,mul(calc(i1.fi,i2.fi),mul(i1.se,i2.se)));
	}
	cout<<ans<<"\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值