归并排序 [20220210模拟赛] 归并排序/组合数学/树状数组
题意
给定长为 \(2^k\) 的排列 \(p\),你要对这个排列进行归并排序。但当当前序列长度为 \(2\) 的时候,有 \(\dfrac{1}{2}\) 的概率会把两个数的位置交换,\(\dfrac{1}{2}\) 的概率原封不动。现在有 \(q\) 次操作:
1 x y:交换 \(p_x,p_y\)2 x y:查询排完序后的序列 \(p'\) 中 \(p'_y=p_x\) 的概率
题解
我们考虑分别在 \(2i-1\) 与 \(2i\) 位置的两个数 \(x,y\),那么底层递归的时候我们就会对 \(x,y\) 进行随机交换。
不妨设 \(x<y\)。
如果交换后 \(x\) 仍然在 \(y\) 前面那自然啥事没有。如果 \(x\) 在 \(y\) 后面,我们考虑归并的过程:
不妨设 \(a>y\),那么 \(a>y>x\)。
进行归并时,先比较 \(a,y\)。发现 \(y\) 比较小,先将 \(y\) 归并到新数组内;接着比较 \(a,x\),发现 \(x\) 比较小,因此紧接着就会将 \(x\) 归并到新数组内。推广一下就是,如果相邻两数 \(x,y\) 在交换后不满足升序,那么 \(x,y\) 无论如何都是相邻的,且 \(x\) 在 \(y\) 后面。
如何让排完序后 \(x,y\) 一直相邻?原本排完序后 \(y\) 后面应该是 \(y+1\),但现在我们要让它是 \(x\),那么很简单,把 \(x\) 看做一个 \(y\) 与 \(y+1\) 之间的数就行了。
这样一来,对每个 \(p_{2i-1}\) 与 \(p_{2i}\),设其中较小的为 \(x\),较大的为 \(y\),我们赋予 \(x\) 两种权值:一种就是 \(x\),一种是 \(y\) 与 \(y+1\) 之间的一个权值。\(y\) 自然只有一种取值,就是 \(y\)。
接下来考虑我们的询问:\(p_a\) 排完序后应当在 \(b\) 的位置。 这实际上就是说,对于每个数,我们需要选取它的一种可能的取值,使得 \(\le p_a\) 的数恰有 \(b\) 个。
对每个数 \(p_i\),我们记 \(u_i\) 为其较小的可能值,\(w_i\) 为其较大的可能值,则有 \(u_i\le w_i\)。如果 \(p_i\) 只有一种取值,我们认为 \(u_i=w_i=p_i\)。
根据 \(u_i,w_i\) 与 \(p_a\) 的大小关系,我们将所有数分为三类:
- \(p_a< u_i< w_i\):这种数必然会放在 \(p_a\) 后面,可以直接不管它们。
- \(u_i< w_i< p_i\):这种数必然会放在 \(p_a\) 前面。设这种数的数量为 \(r_2\)。
- \(u_i< p_i< w_i\):如果选了 \(u_i\),那么就会放在 \(p_a\) 前面;否则就会放在后面。设这种数的数量为 \(r_1\),那么可以发现此时 \(p_a\) 前面还需要 \(b-1-r_2\) 个数,因此我们要做的实际上就是从这 \(r_1\) 个数中选出 \(b-1-r_2\) 个数,让这些数选取 \(u_i\),补上前面的空缺;剩下的选择 \(w_i\),放在后面。那么方案数正是你所熟悉的组合数:\(C_{r_1}^{b-1-r_2}\)。
现在我们只需要求出 \(r_1\) 与 \(r_2\) 即可。
使用两个值域上的树状数组维护 \(\{u_n\},\{w_n\}\) 中 \(< p_a\) 的数的个数,如果 \(\{w_n\}\) 中 \(<p_a\) 的数有 \(c_1\) 个,\(\{u_n\}\) 中 \(< p_a\) 的数有 \(c_2\) 个,敏锐的你或许早已注意到,\(r_1\) 的值正是 \(c_1\), 而 \(r_2\) 则等于 \(c_2-c_1\)。
原题还需要进行修改:交换两个数的位置。由于涉及的修改量只有 \(O(1)\) 个,我们可以直接计算产生的贡献差,更新树状数组即可。
需要注意的是,\(p_a\) 本身就可能有两种取值,我们需要根据 \(p_a\) 到底有几种取值进行分类讨论。
总的时间复杂度为 \(O((n+q)\log n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
return x*f;
}
const int MN=1e5+5;
int n,q;
int p[MN];
struct BIT{
int c[MN<<1];
int lowbit(int x){return x&(-x);}
void add(int x,int k){for(int i=x;i<=2*n+1;i+=lowbit(i))c[i]+=k;}
int query(int x){int res=0;for(int i=x;i;i-=lowbit(i))res+=c[i];return res;}
}S,T;
void del(int id){
if(id%2==0)id--;
int x=p[id],y=p[id+1];
if(x>y)swap(x,y);
T.add(2*x,-1),S.add(2*y+1,-1);
T.add(2*y,-1),S.add(2*y,-1);
}
void ins(int id){
if(id%2==0)id--;
int x=p[id],y=p[id+1];
if(x>y)swap(x,y);
T.add(2*x,1),S.add(2*y+1,1);
T.add(2*y,1),S.add(2*y,1);
}
int ifac[MN],fac[MN];
const int mod=1e9+7;
int ksm(int x,int y,int p=mod){
int res=1;
for(int i=y;i;i>>=1,x=x*x%p)if(i&1)res=res*x%p;
return res;
}
int inv(int x,int p=mod){return ksm(x,p-2,p)%p;}
void init(){
fac[0]=ifac[0]=1;for(int i=1;i<=n;i++)ifac[i]=ifac[i-1]*inv(i)%mod,fac[i]=fac[i-1]*i%mod;;
}
int C(int x,int y){
if(x<y||y<0)return 0;
return fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}
int valx(int x){
return 2*p[x];
}
int valy(int x){
if(x%2==0){
int a=p[x-1],b=p[x];
if(a>b)return 2*a+1;
else return 2*b;
}
else{
int a=p[x+1],b=p[x];
if(a>b)return 2*a+1;
else return 2*b;
}
}
signed main(void){
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
n=read();init();
for(int i=1;i<=n;i++)p[i]=read();
for(int i=1;i*2<=n;i++)ins(i*2-1);
q=read();
while(q--){
int op=read(),x=read(),y=read();
if(op==1){
del(x),del(y);
swap(p[x],p[y]);
ins(x),ins(y);
}
else{
int ans=0;
if(valx(x)==valy(x)){
int cnt2=S.query(valx(x)-1),cnt1=T.query(valx(x)-1);
cout<<C(cnt1-cnt2,y-1-cnt2)%mod*ksm(inv(2),cnt1-cnt2)%mod<<endl;
continue;
}
int cnt2=S.query(valx(x)-1),cnt1=T.query(valx(x)-1);
ans=(ans+C(cnt1-cnt2,y-1-cnt2)%mod*ksm(inv(2),cnt1-cnt2+1)%mod)%mod;
cnt2=S.query(valy(x)-1),cnt1=T.query(valy(x)-1)-1;
ans=(ans+C(cnt1-cnt2,y-1-cnt2)%mod*ksm(inv(2),cnt1-cnt2+1)%mod)%mod;
cout<<ans<<endl;
}
}
return 0;
}

模拟赛中遇到的一道好题。
浙公网安备 33010602011771号