bzoj 1901 有更新区间第k大 树状数组套可持久化线段树

http://www.lydsy.com/JudgeOnline/problem.php?id=1901

我的可持久化线段树消耗的内存太大了,在zoj超内存

如果对 上次的那题无修改的区间第k大的可持久化做法足够了解http://blog.csdn.net/haha593572013/article

那么稍微理解一下就能理解这道题了

在求区间第k大的时候,我们的本质其实是求出一棵包含且只包含所求区间信息的线段树,然后在整棵线段树中查询区间第k大的数,当然这棵线段树是通过做差得出来的,其实就是两个“前缀线段树”相减得出来的,即保存1~y区间信息的线段树 与  保存1~ x 区间信息的线段树节点信息相减,因为上题没有更新,所以每次直接从i-1时刻的线段树插入一个数得到i时刻的线段树,每次得到的新的线段树保存的信息都是1~ i 的信息,即都是前缀的信息

注意到了吗?前缀!!!那么如果有更新的操作的话,我们只需要维护好前缀的信息就可以了

具体做法是用树状数组来维护前缀(线段树)和,树状数组的每个节点又是一棵线段树,所以复杂度就要多乘一个log(n),即nlog^2(n)

求解x y区间第k大的数的时候还是一样,1到y的信息减去1到x-1的信息

具体的细节大家耐心研究下吧,蛮有意思的(树套树)

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 120000;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
 
int ls[maxn*20],rs[maxn*20],sum[maxn*20];
int root[maxn],tot;
  
void build(int l,int r,int &rt)
{
    rt=++tot;
    sum[rt]=0;
    if(l==r) return ;
    int m=l+r>>1;
    build(l,m,ls[rt]);
    build(m+1,r,rs[rt]);
}
void update(int last,int p,int l,int r,int &rt,int v)
{
    rt=++tot;
    ls[rt]=ls[last]; rs[rt]=rs[last];sum[rt]=sum[last]+v;
    if(l==r) return ;
    int m=l+r>>1;
    if(p <= m) update(ls[last],p,l,m,ls[rt],v);
    else update(rs[last],p,m+1,r,rs[rt],v);
}
int cc,n,m;
int num[maxn];
char op[10010][3];
const int NN = 10010;
int san[NN*2],P[NN],Q[NN],K[NN];
int L[30],R[30];
int N,M;
int query(int l,int r,int k)
{
    if(l==r)     return l;
    int m=l+r>>1;
    int suma=0,sumb=0;
    for(int i=1;i<=N;i++) suma+=sum[ls[L[i]]];
    for(int i=1;i<=M;i++) sumb+=sum[ls[R[i]]];
    int del=sumb-suma;
    if(k <= del) {
        for(int i=1;i<=N;i++) L[i]=ls[L[i]];
        for(int i=1;i<=M;i++) R[i]=ls[R[i]];
        return query(l,m,k);
    }  else  {
        for(int i=1;i<=N;i++) L[i]=rs[L[i]];
        for(int i=1;i<=M;i++) R[i]=rs[R[i]];
        return query(m+1,r,k-del);
    }
}
int ask(int l,int r,int k)
{
    N=0,M=0;
    for(;l>0;l-=l&-l) L[++N]=root[l];
    for(;r>0;r-=r&-r) R[++M]=root[r];
    return query(1,cc,k);
}
void Bit_update(int x,int val,int flag)
{
    for(;x<=n;x+=x&-x) 
    {
        update(root[x],val,1,cc,root[x],flag);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&num[i]);
        san[++cc]=num[i];
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%s%d%d",op[i],&P[i],&Q[i]);
        if(op[i][0]=='Q'){  scanf("%d",&K[i]);  }
        else    san[++cc]=Q[i];
    }
    sort(san+1,san+1+cc);
    cc=unique(san+1,san+1+cc)-san-1;
    for(int i=1;i<=n;i++) num[i]=lower_bound(san+1,san+1+cc,num[i])-san;
    build(1,cc,root[0]);
    for(int i=1;i<=n;i++)    Bit_update(i,num[i],1);
    for(int i=1;i<=m;i++) {
        if(op[i][0]=='Q'){
            int id=ask(P[i]-1,Q[i],K[i]);
            printf("%d\n",san[id]);
        } else {
            int pos=lower_bound(san+1,san+1+cc,Q[i]) - san;
            Bit_update(P[i],num[P[i]],-1);
            num[P[i]] = pos;
            Bit_update(P[i],num[P[i]],1);
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值