模板 树状数组套主席树查询动态区间k小

模板,动态区间 k k k小:

给定一个长度为 n n n的数组, m m m次操作,每次操作只可能是下面其中之一:

(1)给定 l , r , k l,r,k l,r,k,求区间 [ l , r ] [l,r] [l,r] k k k小元素

(2)给定 x , y x,y x,y,把 a [ x ] a[x] a[x]改为 y y y

树状数组套主席树解法:

让我们回顾一下区间静态区间 k k k小的方法,利用主席树可以融合多颗线段树的性质,保存 i i i的前缀权值线段树,做差获得 [ l , r ] [l,r] [l,r]的权值线段树,直接在树上二分。显然, i i i的前缀权值线段树仅需要在 i − 1 i-1 i1的前缀权值线段树上修改即可得到。总而言之,获得 [ l , r ] [l,r] [l,r]的权值线段树,再在树上二分就是本质思想。

不妨在上面的思路上继续思考,我们把 a [ x ] a[x] a[x]改成 y y y,那么只需要在 [ x , n ] [x,n] [x,n]的前缀上都修改一遍即可达成目的,可是这样的时间复杂度是 O ( n m l o g n ) O(nmlogn) O(nmlogn)的, m m m是修改次数,太慢

再进一步,我们能不能加速这个修改过程?由于是前缀线段树,并且存在区间求和,于是我们考虑树状数组,令树状数组每个点都是一个线段树,即主席树上的一个根,如果我们需要修改 x x x位置的,能将原来单次修改的时间复杂度由 O ( n l o g n ) O(nlogn) O(nlogn)优化为 O ( l o g 2 n ) O(log^2n) O(log2n)。那考虑如何获得差值权值线段树,在树状数组上,我们的区间求和策略是获得他们的前缀和做差,但由于树状数组存的是根,没有直接的前缀和,于是,每个根我们只存放一些链,也就是部分的线段树,当我们求得前缀和后,所有的链被合并在一起,成为一颗完整的线段树,这样就可以做差获得差值的权值线段树了

由于我们存放的是根,我们在合并后的线段树上二分,合并之后的线段树不好保存,不妨直接在部分树上分别二分,权值取所有对应点的总和,因此需要一个数组来保存他们的根,并且只有根满足 l o w b i t lowbit lowbit规则(因为只有根在树状数组上),所以预先处理处根的数组,然后树上二分时所有点同时向左/右子树移动。在线段树上二分的时间复杂度时 O ( l o g n ) O(logn) O(logn),同时二分 O ( l o g n ) O(logn) O(logn)条,于是单词查询时间复杂度是 O ( l o g 2 n ) O(log^{2}n) O(log2n),总时间复杂度是 O ( n l o g 2 n ) O(nlog^{2}n) O(nlog2n)。每次操作在一个根上最多插入一条长 O ( l o g n ) O(logn) O(logn)的链,每次操作修改了 O ( l o g n ) O(logn) O(logn)个根,于是总空间复杂度是 O ( n l o g 2 n ) O(nlog^{2}n) O(nlog2n)

#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;

int n,m;

struct question
{
    string s;
    int l,r,k;
}q[100005]; 

int a[100005],vv[200005];
int tree[40000005],cnt,version[100005];
int lson[40000005],rson[40000005],vp,len;

inline int lowbit(int x){return -x&x;}
inline int& ls(int x){return lson[x];}
inline int& rs(int x){return rson[x];}
inline void push_up(int x){tree[x]=tree[ls(x)]+tree[rs(x)];}

void update(int nl,int l,int r,int x,int k)
{
    if(l==r){tree[x]+=k;return;}
    int mid=l+r>>1;
    if(nl<=mid)
    {
        if(!ls(x)) ls(x)=++cnt;
        update(nl,l,mid,ls(x),k);
    }
    else
    {
        if(!rs(x)) rs(x)=++cnt;
        update(nl,mid+1,r,rs(x),k);
    }
    push_up(x);
}

void add(int x,int k,int v)
{
    while(x<=n)
    {
        if(!version[x]) version[x]=++cnt;
        update(k,1,len,version[x],v);
        x+=lowbit(x);
    }
}

int tmp1[100005],pp1,tmp2[100005],pp2;

int solve(int l,int r,int x,int k)
{
    // printf("x=%d\n",x);
    if(l==r) return l;
    int mid=l+r>>1,sum=0;
    for(int i=1;i<=pp1;i++) sum-=tree[ls(tmp1[i])];
    for(int i=1;i<=pp2;i++) sum+=tree[ls(tmp2[i])];
    if(k<=sum)
    {
        for(int i=1;i<=pp1;i++) tmp1[i]=ls(tmp1[i]);
        for(int i=1;i<=pp2;i++) tmp2[i]=ls(tmp2[i]);
        return solve(l,mid,ls(x),k);
    }
    else
    {
        for(int i=1;i<=pp1;i++) tmp1[i]=rs(tmp1[i]);
        for(int i=1;i<=pp2;i++) tmp2[i]=rs(tmp2[i]);
        return solve(mid+1,r,rs(x),k-sum);
    }
}

int solve(int l,int r,int k)
{
    pp1=pp2=0;
    for(int i=l-1;i;i-=lowbit(i)) tmp1[++pp1]=version[i];
    for(int i=r;i;i-=lowbit(i)) tmp2[++pp2]=version[i];
    return vv[solve(1,len,1,k)];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        vv[++len]=a[i];
    }
    for(int i=1;i<=m;i++)
    {
        auto& [s,l,r,k]=q[i]; cin>>s;
        if(s=="Q") cin>>l>>r>>k;
        else
        {
            cin>>l>>r;
            vv[++len]=r;
        }
    }
    sort(vv+1,vv+1+len); len=unique(vv+1,vv+1+len)-vv-1;
    for(int i=1;i<=n;i++) a[i]=lower_bound(vv+1,vv+1+len,a[i])-vv;
    for(int i=1;i<=m;i++)
        if(q[i].s=="C") q[i].r=lower_bound(vv+1,vv+1+len,q[i].r)-vv;
    for(int i=1;i<=n;i++) add(i,a[i],1);
    for(int i=1;i<=m;i++)
    {
        auto& [s,l,r,k]=q[i];
        if(s=="Q") printf("%d\n",solve(l,r,k));
        else
        {
            add(l,a[l],-1);
            add(l,a[l]=r,1);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值