动态主席树(主席树套树状数组)zoj 2112 Dynamic Rankings 详解

既然看到这个题了,相信静态主席树求区间第k大应该不成问题了, 此题在原有基础上,不过添加了单点修改的要求。
那么来说一下大体思路:

首先,用题目给的n个原始数据像之前一样建立主席树。对于修改,我们不是要真的去修改我们已经建立好的主席树,而是用另一个数组来维护修改所造成的变化,例如:要求 i 位置到 j 位置的第k大,之前的策略是找到第 i-1 和 第 j 棵树,两个权值线段树根据左右子节点的权值下搜,那么现在添加了修改操作,我们就必须考虑 i-1 和 j 树之间的变化,把变化也加入其中,就可以了。

原树的数组中第 i 棵树维护的是前 i 个位置的权值线段树,修改第 i 位置,对 i ~ n 的树有影响,那么,假设说我们再设 n 个权值线段树,每棵树来表示第 i 个位置上的造成的修改,如果我们要求第 i 个位置修改后正确的权值线段树信息,就需要统计1 ~ i 位置的全部修改,所以我们可以用树状数组的思想来维护这个权值线段树数组。

void add(int i, int x, int val){//第i棵权值线段树上,x位置,加val
    while(i<=n){
        updata(s[i], s[i], 1, tot, x, val);
        i += lowbit(i);
    }
}

然后是怎么查找的问题。假如说现在要查找 i+1 位置和 j 位置之间的第k大,我们要不断下移结点,直到节点表示的范围长度为 1 。开始时结点范围是 1 ~ tot, 对原主席树 i+1 树和 j 树该结点的左结点权值做差,然后取用树状数组 s ,求出前 i+1 树和 j 树当前结点的的左结点权值的变化的总和做差,再和前者相加,得到正确的左结点权值,和 k 比较后左移或右移。在此期间的问题是怎么找到接下来 s 数组中第 i+1 和 j 树的当前结点。所以我们用再开一个一维数组 use 来维护,s 中的第 x 个树到了当前深度的结点的编号 use[x]。

int get(int x){//到了当前深度,前x课树的对应结点的左结点变化总和
    int ret=0;
    while(x>0){
        ret += sum[ L[ use[x] ] ];
        x -= lowbit(x);
    }
    return ret;
}

整个过程比较难懂的就是利用 s 数组来查找当前结点的变化,顺着代码慢慢看一遍就好了,注释写的挺全的

Code:

#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long LL;
using namespace std;
const int maxn=6e4+500;
const int maxm=1e4+500;

int T[maxn], s[maxn]/*维护变化的主席树树状数组*/, L[maxn*32], R[maxn*32], sum[maxn*32];
int cur/*主席树加点*/, tot/*离散化数组长度*/, n;
int data[maxn];//原始数据
int num[maxn];//离散化
int use[maxn];//! 对于s,第i棵树,到目前为止,所在深度,对应的节点
              //! 概况来讲,use随着静态主席树的下潜而更新
struct Q{
    int a, b, k;
    bool tp;//1问 0改
}q[maxm];

int gs(int x){return lower_bound(num+1, num+1+tot, x)-num;}

void build(int& rt, int l, int r){
    rt = ++cur;
    sum[rt] = 0;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(L[rt], l, mid);
    build(R[rt], mid+1, r);
}
void updata(int &rt, int pre, int l, int r, int x, int val){
    rt = ++cur;
    L[rt] = L[pre], R[rt] = R[pre];
    sum[rt] = sum[pre] + val;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(x<=mid)  updata(L[rt], L[pre], l,   mid, x, val);
    else        updata(R[rt], R[pre], mid+1, r, x, val);
}

int lowbit(int x) {return x&(-x); }

void add(int i, int x, int val){
    while(i<=n){
        updata(s[i], s[i], 1, tot, x, val);
        i += lowbit(i);
    }
}
int get(int x){//前x课树的变化总和(当前深度的节点)
    int ret=0;
    while(x>0){
        ret += sum[ L[ use[x] ] ];
        x -= lowbit(x);
    }
    return ret;
}

int query(int p1, int p2, int t1, int t2, int l, int r, int k){
    if(l==r) return l;
    int mid=(l+r)>>1;
    int sz=get(p2) - get(p1) + sum[L[t2]] - sum[L[t1]];//左区间的权值
    if(k<=sz){
        for(int i=p1; i>0; i-=lowbit(i)) use[i] = L[use[i]]; //下潜
        for(int i=p2; i>0; i-=lowbit(i)) use[i] = L[use[i]];
        return query(p1, p2, L[t1], L[t2], l, mid, k);
    }else{
        for(int i=p1; i>0; i-=lowbit(i)) use[i] = R[use[i]]; //下潜
        for(int i=p2; i>0; i-=lowbit(i)) use[i] = R[use[i]];
        return query(p1, p2, R[t1], R[t2], mid+1, r, k-sz);
    }
}


int main()
{
    char cmd[5];
    int t, m;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        cur = tot = 0;//! init
        for(int i=1; i<=n; i++){
            scanf("%d",&data[i]);
            num[++tot] = data[i];
        }
        for(int i=1; i<=m; i++){
            scanf("%s",cmd);
            if(cmd[0]=='Q'){
                scanf("%d%d%d",&q[i].a, &q[i].b, &q[i].k);
                q[i].tp = 1;
            }
            if(cmd[0]=='C'){
                scanf("%d%d",&q[i].a, &q[i].b);
                q[i].tp = 0;
                num[++tot] = q[i].b;
            }
        }
        sort(num+1, num+1+tot);
        tot = unique(num+1, num+1+tot) - (num+1);

        build(T[0], 1, tot);
        for(int i=1; i<=n; i++) updata(T[i], T[i-1], 1, tot, gs(data[i]) , 1);//静态树建立完成
        for(int i=1; i<=n; i++) s[i] = T[0];

        for(int i=1; i<=m; i++){
            if(q[i].tp==1){//询问
                for(int j=q[i].a-1; j>0; j-=lowbit(j)) use[j] = s[j];
                for(int j=q[i].b;   j>0; j-=lowbit(j)) use[j] = s[j];//s数组准备同步下潜
                printf("%d\n", num[ query(q[i].a-1, q[i].b, T[q[i].a-1], T[q[i].b], 1, tot, q[i].k) ] );
            }
            if(q[i].tp==0){//修改
                add(q[i].a, gs( data[q[i].a] ), -1);
                add(q[i].a, gs( q[i].b ), 1);
                data[q[i].a] = q[i].b;
            }
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值