[JZOJ5171]归并排序

28 篇文章 0 订阅
15 篇文章 0 订阅

题目大意

给定一个 n 的排列{Pn},保证 n 2的整数次幂。有 q 次操作,分以下两种:
 交换 Px Py
  对这个排列进行一个有bug的归并排序:在分治到长度为 2 时会以1/2的概率选择是否交换两个数。然后求 Px 排完序之后排名为 y 的概率是多少。
答案对109+7取模。

1n216,1q105


题目分析

观察发现,出现bug的时候,较小的那个数在排完序之后一定在较大那个数的后一位置。因此我们将出现bug的情况看成较小的数变成较大的数 +0.5 显然是等价的。
然后问题就可以变成 n 个数,每个数有两种等概率的取值,数与数之间取值互相独立,并且两个不同的数不会有相同的取值,求某个数排完序之后排名为y的概率。
先考虑怎么做询问,我枚举这个数的取值,然后相当于我要求恰好有 y1 个数排完序小于它。我们可以求出一定小于它的数的个数以及有 1/2 概率小于它的数的个数,然后组合数算一算。
然后这个个数怎么统计呢?其实就是一个二维数点的问题,因为要支持修改,因此我们可以考虑使用嵌套数据结构或者定期重构的KD-tree来解决,时间复杂度在 O(nlog2n) O(nn) 左右,不是特别优秀。
怎么优化时间呢?可以发现这题的点有特殊性质,就是它 x 坐标一定小于等于y坐标。这样我们可以把它转化为一维上的数区间问题,补集转化一下就可以使用树状数组在 O(nlogn) 的时间复杂度解决。


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int buf[30];

void write(int x)
{
    if (x<0) putchar('-'),x=-x;
    for (;x;x/=10) buf[++buf[0]]=x%10;
    if (!buf[0]) buf[++buf[0]]=0;
    for (;buf[0];putchar('0'+buf[buf[0]--]));
}

const int itwo=500000004;
const int P=1000000007;
const int N=1<<18;

int lowbit(int x){return x&-x;}

int quick_power(int x,int y)
{
    int ret=1;
    for (;y;y>>=1,x=1ll*x*x%P) if (y&1) ret=1ll*ret*x%P;
    return ret;
}

int fact[N],invf[N],POW[N],rg[N][2];
int a[N];
int n,q,d,lim;

int C(int n,int m){return 1ll*fact[n]*invf[m]%P*invf[n-m]%P;}

struct Fenwick_tree
{
    int v[N];

    void modify(int x,int delta){for (;x<=lim;x+=lowbit(x)) v[x]+=delta;}

    int query(int x)
    {
        int ret=0;
        for (;x;x-=lowbit(x)) ret+=v[x];
        return ret;
    }
}t[2];

void ADD(int x){t[0].modify(rg[x][0],1),t[1].modify(rg[x][1],1);}

void DEL(int x){t[0].modify(rg[x][0],-1),t[1].modify(rg[x][1],-1);}

void pre()
{
    d=quick_power(quick_power(2,n),P-2);
    fact[0]=1;
    for (int i=1;i<=n;++i) fact[i]=1ll*fact[i-1]*i%P;
    POW[0]=1;
    for (int i=1;i<=n;++i) POW[i]=1ll*POW[i-1]*itwo%P;
    invf[n]=quick_power(fact[n],P-2);
    for (int i=n;i>=1;--i) invf[i-1]=1ll*invf[i]*i%P;
    for (int i=0;i<n;++i)
        if (a[i]>a[i^1]) rg[i][0]=rg[i][1]=(a[i]<<1)-1;
        else rg[i][0]=(a[i]<<1)-1,rg[i][1]=a[i^1]<<1;
    for (int i=0;i<n;++i) ADD(i);
}

int calc(int x,int y)
{
    int cnt=t[1].query(x-1),cnt_=t[0].query(x)-1;
    return cnt>y?0:1ll*C(cnt_-cnt,y-cnt)*POW[cnt_-cnt]%P;
}

int main()
{
    freopen("sort.in","r",stdin),freopen("sort.out","w",stdout);
    lim=(n=read())<<1;
    for (int i=0;i<n;++i) a[i]=read();
    pre();
    for (q=read();q--;)
    {
        int opt=read(),x=read()-1,y=read()-1;
        if (opt==1)
        {
            if (x==y) continue;
            DEL(x),DEL(x^1);
            if ((x|1)!=(y|1)) DEL(y),DEL(y^1);
            swap(a[x],a[y]);
            if (a[x]>a[x^1]) rg[x][0]=rg[x][1]=(a[x]<<1)-1,rg[x^1][0]=(a[x^1]<<1)-1,rg[x^1][1]=a[x]<<1;
            else rg[x][0]=(a[x]<<1)-1,rg[x][1]=a[x^1]<<1,rg[x^1][0]=rg[x^1][1]=(a[x^1]<<1)-1;
            if (x|1!=y|1) if (a[y]>a[y^1]) rg[y][0]=rg[y][1]=(a[y]<<1)-1,rg[y^1][0]=(a[y^1]<<1)-1,rg[y^1][1]=a[y]<<1;
            else rg[y][0]=(a[y]<<1)-1,rg[y][1]=a[y^1]<<1,rg[y^1][0]=rg[y^1][1]=(a[y^1]<<1)-1;
            ADD(x),ADD(x^1);
            if ((x|1)!=(y|1)) ADD(y),ADD(y^1);
        }
        else
        {
            int ans=1ll*(calc(rg[x][0],y)+calc(rg[x][1],y))*itwo%P;
            write(ans),putchar('\n');
        }
    }
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值