Mergesort Strikes Back
題意
給你兩個正整數 \(n,k\),問長度為 \(n\) 的隨機排列,做深度為 \(k\) 的歸併排序(\(k=1\) 就是不排)後,期望逆序對個數。對給定素數取模。
思路
首先如果 \(k \ge \log n\) 就可以排好序,逆序對個數為 \(0\)。
否則,假設排列給定,那麼最後一次分治形成的若干個長度為 \(len\) 或者 \(len+1\) 的區間內相對順序是不變的,因此我們可以先算出這些區間的逆序對。對於四個隨機的排列,一個長度為 \(len\) 的區間的逆序對個數期望是,對於任意一對數字,都有 \(\frac{1}{2}\) 的機率正序,\(\frac{1}{2}\) 的機率逆序,所以期望逆序對個數是 \(\frac{len(len-1)}{4}\)。
然後我們探尋歸併排序的性質,計算不同區間的逆序對個數。
考慮一個合併的過程。兩個不一定有序的序列 \(A,B\),先比較 \(a_1,b_1\) 的大小,若 \(a_1<b_1\),則插入 \(a_1\),同時 \(a_1\) 後面跟著的若干個比 \(a_1\) 小的 \(a_i\) 也會插入,假設一共插入了 \(j\) 個數字。然後再比較 \(a_{j+1}\) 和 \(b_1\),以此類推。
我們發現其實就是按照字首最大值把 \(A,B\) 分成若干塊,然後按照字首最大值對這些塊排序。
我們計算逆序對個數,考慮 \((a_i,b_j)\) 的貢獻。設 \(a_{1 \sim i},b_{1 \sim j}\) 的最大值為 \(x\),假設 \(x\) 是 \(a_i\),那麼 \(a_i\) 一定會放在這兩個字首的所有數的最後,因此沒有逆序對,\(b_j\) 是最大值情況同理。
若 \(x\) 不是 \(a_i\) 或 \(b_j\),如果 \(x\) 在 \(a_{1 \sim i-1}\),則 \(x\) 後面那一坨,當然包括了 \(a_i\) 會放到 \(b_j\) 的後面,因為 \(A,B\) 都是隨機的,所以有 \(\frac{1}{2}\) 的機率 \(a_i > b_j\),同樣 \(\frac{1}{2}\) 的機率 \(a_i < b_j\),若 \(x\) 在 \(b_{1 \sim j-1}\) 同理。則逆序對個數期望是 \(\frac{1}{2}\) 的。
因此對於 \(a_i,b_j\),有 \(\frac{2}{i+j}\) 的機率沒有逆序對,有 \(\frac{i+j-2}{i+j}\) 的機率有 \(\frac{1}{2}\) 的逆序對,所以我們要計算 \(\sum_i \sum_j \frac{i+j-2}{2(i+j)}\)。
變成:
求這個東西是 \(len^2\) 的,\(O(len)\) 預處理 \(\frac{x-2}{2x}\) 的字首和就可以變成 \(O(len)\) 了。如果你的預處理每次都要求逆元,可能時間是帶 \(\log\) 的。
實際上,因為本質上我們是在按照每一小塊的字首最大值排序,所以我們可以拋棄這個歸併的過程,計算任意兩個小塊的貢獻。
由於小塊的長度只有兩種,所以最終複雜度是 \(O(len)\) 的,常數其實應該也不算很大。
題目的歸併分治時是令 \(mid=\lfloor \frac{l+r}{2} \rfloor\),把 \([l,r]\) 分成 \([l,mid]\) 和 \([mid+1,r]\) 的,和我平常寫的線段樹一樣。
計算每種長度的一對小塊有多少個,一個不用動腦的做法是類似於 CDQ 分治統計。當然這樣做是帶 $\log $ 的,顯然有更優美的做法。
AC Code
思維難度較低的常數巨大寫法。
#include<bits/stdc++.h>
//#define LOCAL
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
const int N=1e5+7;
int n,k,mod;
ll pre[N];
ll add(ll a,ll b) {return a+b>=mod?a+b-mod:a+b;}
ll ksm(ll a,ll b) {
ll s=1;
while(b) {
if(b&1) s=s*a%mod;
a=a*a%mod;
b>>=1;
}
return s;
}
void init(int n) {
rep(i,3,n) {
pre[i]=1ll*(i-2)*ksm(i*2,mod-2)%mod;
pre[i]=add(pre[i-1],pre[i]);
}
}
int len;
struct node {
ll x[4],y[2];
node () {memset(x,0,sizeof(x)),memset(y,0,sizeof(y));}
};
node operator + (node a,node b) {
a.x[0]+=a.y[0]*b.y[0]%mod;
a.x[1]+=a.y[0]*b.y[1]%mod;
a.x[2]+=a.y[1]*b.y[0]%mod;
a.x[3]+=a.y[1]*b.y[1]%mod;
rep(i,0,1) a.y[i]+=b.y[i];
rep(i,0,3) a.x[i]+=b.x[i];
rep(i,0,1) a.y[i]%=mod;
rep(i,0,3) a.x[i]%=mod;
return a;
}
node cal(int l,int r,int k) {
if(k==0) {
node a;
if(r-l+1==len) a.y[0]=1;
else a.y[1]=1;
return a;
}
int mid=(l+r)>>1;
node ls=cal(l,mid,k-1);
node rs=cal(mid+1,r,k-1);
return ls+rs;
}
ll ans;
int main(){
#ifdef LOCAL
freopen("in.txt","r",stdin);
// freopen("my.out","w",stdout);
#endif
sf("%d%d%d",&n,&k,&mod);
k--;
if(k>__lg(n)) {
pf("0\n");
return 0;
}
len=n>>k;
if(k==0) {
pf("%lld\n",1ll*n*(n-1)%mod*ksm(4,mod-2)%mod);
return 0;
}
init(n);
node a=cal(1,n,k);
ans=add(1ll*len*(len-1)%mod*ksm(4,mod-2)%mod*a.y[0]%mod,1ll*(len+1)*len%mod*ksm(4,mod-2)%mod*a.y[1]%mod);
rep(i,1,len) {
ans=add(ans,add(pre[i+len],mod-pre[i])*a.x[0]%mod);
ans=add(ans,add(pre[i+len+1],mod-pre[i])*a.x[1]%mod);
}
rep(i,1,len+1) {
ans=add(ans,add(pre[i+len],mod-pre[i])*a.x[2]%mod);
ans=add(ans,add(pre[i+len+1],mod-pre[i])*a.x[3]%mod);
}
pf("%lld\n",ans);
}