洛谷 P5105 不强制在线的动态快速排序

本文探讨了一种动态快速排序算法,旨在解决集合S的动态排序问题。通过维护一个允许重复的集合,支持区间插入和快速排序后的异或和计算,采用线段树优化区间插入操作,实现高效查询。

P5105 不强制在线的动态快速排序

题目背景

曦月最近学会了快速排序,但是她很快地想到了,如果要动态地排序,那要怎么办呢?

题目描述

为了研究这个问题,曦月提出了一个十分简单的问题

曦月希望维护一个允许重复的集合\(S\),支持:

  • 插入\([L, R]\),也就是插入\(L, L + 1 ... , R\),这\(R - L + 1\)个数
  • 询问\(Sort(S)\)

\(Sort(S)\)的定义为:

我们将集合\(S\)中的元素从小到大按照快速排序排好序,记为\(a_1, a_2 ... a_n\)

那么,\(Sort(S) = \bigoplus \limits_{i = 2}^n (a_i^2 - a_{i - 1}^2)\),其中\(\bigoplus\)表示异或和

关于异或的定义,请咨询度娘

输入输出格式

输入格式:

第一行,一个数\(q\)

\(q\)行,或者是\(1\;l\;r\),表示插入\([l, r]\),或者是\(2\),表示一次询问

输出格式:

对于每个询问,一行一个答案

说明

对于\(30\)分的数据,\(q \leqslant 100\)

对于\(50\)分的数据,\(q \leqslant 5 * 10^4\)

对于另外的\(20\)分的数据,满足\(L=R\)

对于\(100\)分的数据,\(q \leqslant 3 * 10^5\)\(1 \leqslant L \leqslant R \leqslant 10^9\)


先考虑单独插入一个区间\([l,r]\)

然后发现贡献是\(\bigoplus_{i=l}^r 2i+1\)

把每个贡献右移一位,可以用发现是求\(\bigoplus_{i=l}^r i\),然后特判一下最后一位的奇偶。

异或前缀和有个结论

int cal(int n)
{
    if(n%4==0) return n;
    if(n%4==1) return 1;
    if(n%4==2) return n+1;
    if(n%4==3) return 0;
}

多组显然可以平衡树,但是写起来很麻烦

于是可以用线段树直接维护区间插入,对每个区间维护最左和最右位置,然后直接合并左右儿子时计算一下贡献就可以了。


Code:

#include <cstdio>
#define int long long
const int N=3e5+10;
int cal(int n)
{
    if(n%4==0) return n;
    if(n%4==1) return 1;
    if(n%4==2) return n+1;
    if(n%4==3) return 0;
}
#define ls ch[now][0]
#define rs ch[now][1]
int sum[N*20],ch[N*20][2],lp[N*20],rp[N*20],is[N*20],tot,root;
void change(int &now,int L,int R,int l,int r)
{
    if(is[now]) return;
    if(!now) now=++tot;
    if(L==l&&R==r)
    {
        is[now]=1;
        if(l!=r) sum[now]=((cal(r-1)^cal(l-1))<<1)|(r-l&1);
        lp[now]=l;
        rp[now]=r;
        return;
    }
    int Mid=L+R>>1;
    if(r<=Mid) change(ls,L,Mid,l,r);
    else if(l>Mid) change(rs,Mid+1,R,l,r);
    else change(ls,L,Mid,l,Mid),change(rs,Mid+1,R,Mid+1,r);
    lp[now]=lp[ls];
    if(!lp[now]) lp[now]=lp[rs];
    rp[now]=rp[rs];
    if(!rp[now]) rp[now]=rp[ls];
    sum[now]=sum[ls]^sum[rs];
    if(rp[ls]&&lp[rs])
        sum[now]^=(lp[rs]+rp[ls])*(lp[rs]-rp[ls]);
}
const int M=1e9;
signed main()
{
    int n;scanf("%lld",&n);
    for(int op,l,r,i=1;i<=n;i++)
    {
        scanf("%lld",&op);
        if(op==2)
            printf("%lld\n",sum[root]);
        else
        {
            scanf("%lld%lld",&l,&r);
            change(root,1,M,l,r);
        }
    }
    return 0;
}

2018.12.16

转载于:https://www.cnblogs.com/butterflydew/p/10127654.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值