传送门
不知道为什么一堆写了 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(k−1)可以直接算。
然后现在需要考虑两个块之间的贡献。
显然我们将每个块按照前缀最大值分成若干块,显然这个归并排序的本质是在对块头进行排序。
考虑两个块,第一个块的第 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+j−2=21−i+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;
}