Mergesort Strikes Back

liyixin發表於2024-10-05

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)}\)

變成:

\[\sum_{i=1}^{len_A} \sum_{j=i+1}^{i+len_B} \frac{j-2}{2j} \]

求這個東西是 \(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);
}

相關文章