[BZOJ1901]Dynamic Rankings(动态主席树)

题目:

我是超链接

题意:

多组数据,对于每组数据,先读入两个值N,M,表示一个N个数的数列,有M次操作。
接下来一行读入数列的值。对于操作分为两种C i j 表示把数列中第I个数的值改为j, Q i j k 表示求区间[I,J]的第k小。

题解:

什么你还修改了?正解就是树状数组套主席树(传说中的树套树?)
这是一道模板啦
很好的讲解
主要的步骤如下
首先我们还是建好我们静态的主席树,注意要把那些改变后加进来的值一同加进来(先空好位置)
对于更新, 我们不改变这些已经建好的树,而是另建一批树S,用来记录更新,而这批线段树,我们用树状数组来维护,树状数组的每个节点都是一颗线段树
当查询的时候, 对树T的操作与静态的一致,另外再加上S树的值就好了
再用下标我是要疯掉的,我们不如每次二分吧。。

下面是关于空间的讨论,设总共不一样的数为s
如果我们建立一棵主席树在上面进行修改的话,我们的空间复杂度是 O(slogslogn) 分分钟被卡
但如果另建一批树S,空间就是 O(nlogs+mlognlogs)

如果你去ZOJ2112的话,多组数据告诉你清零要清root,更小的空间限制告诉你要用更加优秀的空间(第二种方法)

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=50005;
struct hh{int l,r,w;}t[N*50];
int root[N*2],p[30],q[30],s,sz,n,pp,qq,a[N],b[N*2],L[N],R[N],pos[N],K[N];
char O[N];
void insert(int &now,int l,int r,int v,int x){
    t[++sz]=t[now]; now=sz;
    t[now].w+=v;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) insert(t[now].l,l,mid,v,x);
    else insert(t[now].r,mid+1,r,v,x);
}
void bit_insert(int i,int v,int x)
{
    for (;i<=n;i+=i&(-i)) insert(root[i],1,s,v,x);
}
int qurry(int l,int r,int k)
{
    if (l==r) return l;
    int mid=(l+r)>>1,suml=0,sumr=0;
    for (int i=1;i<=pp;i++) suml+=t[t[p[i]].l].w;
    for (int i=1;i<=qq;i++) sumr+=t[t[q[i]].l].w;
    if (sumr-suml>=k)
    {
        for (int i=1;i<=pp;i++) p[i]=t[p[i]].l;
        for (int i=1;i<=qq;i++) q[i]=t[q[i]].l;
        return qurry(l,mid,k);
    }
    else
    {
        for (int i=1;i<=pp;i++) p[i]=t[p[i]].r;
        for (int i=1;i<=qq;i++) q[i]=t[q[i]].r;
        return qurry(mid+1,r,k-(sumr-suml));
    }
}
int work(int l,int r,int k)
{
    pp=0;qq=0;
    if (l>0) p[++pp]=root[l+n];//若l=0则不能将root[n]加到p中 
    q[++qq]=root[r+n];
    //加上原主席树的值 
    for (;l>=1;l-=l&(-l)) p[++pp]=root[l];
    for (;r>=1;r-=r&(-r)) q[++qq]=root[r];
    return qurry(1,s,k);
}
int main()
{
    int T,m;scanf("%d",&T);
    while (T--)
    {
        s=sz=0;
        memset(root,0,sizeof(root));
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;i++) scanf("%d",&a[i]),b[++s]=a[i];
        for (int i=1;i<=m;i++) 
        {
            char st[2];scanf("%s%d%d",st,&L[i],&R[i]); O[i]=st[0];
            if (st[0]=='Q') scanf("%d",&K[i]);
            else b[++s]=R[i];
        }
        sort(b+1,b+s+1);
        s=unique(b+1,b+s+1)-b-1;//利用去重函数得到节点数 
        for (int i=1;i<=n;i++) pos[i]=lower_bound(b+1,b+s+1,a[i])-b;//pos记录每个值在主席树中的位置,第i个位置的数排第几 
        for (int i=1;i<=n;i++) 
        {
            root[i+n]=root[i+n-1];
            insert(root[i+n],1,s,1,pos[i]);
        }
        for (int i=1;i<=m;i++)
          if (O[i]=='Q')
          {
            int ans=work(L[i]-1,R[i],K[i]);
            printf("%d\n",b[ans]);
          }
          else
          {
            bit_insert(L[i],-1,pos[L[i]]);//消除原先值的影响 
            pos[L[i]]=lower_bound(b+1,b+s+1,R[i])-b;//更新pos值 
            bit_insert(L[i],1,pos[L[i]]);//加入更新值的影响 
          }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值