可修改区间k-th number

35 篇文章 0 订阅
4 篇文章 0 订阅

不带修改的区间k-th number

这个众所周知了。可以用主席树实现。
具体的话其实就是开 n 棵权值线段树,动态开节点,由于每次增加一个点最多只会新建log2n个点,所以空间复杂度为 O(nlog2n)
我们在权值线段树上记录该范围数的出现次数,然后可以发现查询的两个区间端点的线段树信息可以相减,于是就可以线段树上二分了。时间复杂度 O(nlog2n)

可修改区间k-th number

%了一波Samjia2000
注意到像上面那样做的话,修改一个点就要把它后面所有点也一起修改,显然TLE。
上面的方法是不能兹瓷修改的。
那我们就需要树套树!

修改操作

我们用一个BIT,BIT上的每个点都是一棵线段树,位置为 x 的线段树记录了其前面lowbit(x)个以BIT的结构过来的点的信息。这是一种变形的前缀和。
(请注意,主席树是真实的前缀和,而用树状数组则是伪前缀和)
修改操作,就在BIT上往后跳,跳到一个点就改一个点。
修改操作,就是BIT套线段树。
单次修改时间复杂度为 O(log22n)

查询操作

有了记录前缀信息的BIT,就能像BIT一样,往前跳。
起初我以为要把所有这些线段树合并到答案里,但这样做麻烦,而且时间很慢。
其实可以先记录好所需的线段树的编号,然后直接进入线段树,在线段树里再把记录的编号拿出来,两个端点相减二分。当在线段树里二分时,所记录的编号里面的所有点也要相应地走到左儿子或右儿子。
查询操作,就是线段树套BIT。
单次查询时间复杂度为 O(log22n)

总结

总的时间复杂度为 O((n+m)log22n)
空间复杂度为 O((n+m)log22n)

Code

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int N=10010,M=2000010;
int n,m,num,a[N],lv[N],rv[N],root[N],ls[M],rs[M],sum[M];
int lowbit(int x){return x&-x;}
void modify(int &v,int l,int r,int x,int p)//single point modify
{
    if(!v) v=++num;
    sum[v]+=p;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(x<=mid) modify(ls[v],l,mid,x,p);
    else modify(rs[v],mid+1,r,x,p);
}
void add(int x,int y,int p)
{
    for(;x<=n;x+=lowbit(x)) modify(root[x],1,n,y,p);
}
void turnL()
{
    fo(i,1,rv[0]) rv[i]=ls[rv[i]];
    fo(i,1,lv[0]) lv[i]=ls[lv[i]];
}
void turnR()
{
    fo(i,1,rv[0]) rv[i]=rs[rv[i]];
    fo(i,1,lv[0]) lv[i]=rs[lv[i]];
}
int query(int l,int r,int k)//k-th number
{
    if(l==r) return l;
    int mid=(l+r)>>1,t=0;
    fo(i,1,rv[0]) t+=sum[rs[rv[i]]];
    fo(i,1,lv[0]) t-=sum[rs[lv[i]]];
    if(t>=k)
    {
        turnR();
        return query(mid+1,r,k);
    }
    else
    {
        turnL();
        return query(l,mid,k-t);
    }
}
int main()
{
    int tp,_,x,y,k;
    scanf("%d",&n);
    fo(i,1,n)
    {
        scanf("%d",&a[i]);
        add(i,a[i],1);
    }
    scanf("%d",&_);
    while(_--)
    {
        scanf("%d %d %d",&tp,&x,&y);
        if(tp==0)
        {
            add(x,a[x],-1);
            a[x]=y;
            add(x,a[x],1);
        }
        else
        {
            scanf("%d",&k);
            lv[0]=rv[0]=0;
            for(int i=y;i;i-=lowbit(i)) rv[++rv[0]]=root[i];
            for(int i=x-1;i;i-=lowbit(i)) lv[++lv[0]]=root[i];
            //record position in BIT
            printf("%d\n",query(1,n,k));
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值