权值线段树

权值线段树

定义:

普通线段树维护的是数列的区间信息,而权值线段树维护了一列数中数的个数。

也就是说,我们的权值线段树就是用线段树维护了一堆桶

例子:

给定一个排列:

\(1,1,1,2,3,4,5\)

其中,一棵线段树的叶子节点维护的是:有几个 \(1\),有几个 \(2\)

他们的父亲节点维护的是有几个 \(1\)\(2\)

区别:

权值线段树按照值域开空间,维护的是个数,而普通线段树则根据数的个数开空间,维护数本身信息。

用途:

权值线段树可以解决 数列第 \(k\) 大/小的问题

整棵线段树的根节点表示整个值域有几个数。

模板:

void pushup(int x){
    t[x]=t[x<<1]+t[x<<1|1];
}

void build(int x,int l,int r){
    if(l==r){
        t[x]=a[l]; return;
    }
    int mid=l+r>>1;
    build(x<<1,l,mid); build(x<<1|1,mid+1,r);
    pushup(x);
}

void update(int x,int l,int r,int c,int cnt){//表示数k的个数多cnt个
    if(l==r){
        t[x]+=cnt; return;
    }
    int mid=l+r>>1;
    if(c<=mid) update(x<<1,l,mid,c,cnt);
    else update(x<<1|1,mid+1,r,c,cnt);
    pushup(x);
}

int query(int x,int l,int r,int c){//查询这个数有多少个
    if(l==r){
        return t[x];
    }
    int mid=l+r>>1;
    if(c<=mid) return query(x<<1,l,mid,c);
    else return query(x<<1|1,mid+1,r,c);
}

查询第 \(k\) 大/小值:

思路:

  • 到每个节点时,如果 右子树的总和大于等于 \(k\),说明第 \(k\) 大值出现在右子树中,则递归进右子树

  • 否则说明此时的第 \(k\) 大值在左子树中,则递归进左子树,注意:此时要 \(k\) 的值减去右子树的总和

  • 如果我们要找的是第 \(7\)大值,右子树总和为 \(4\)\(7−4=3\) ,说明在该节点的第 \(7\) 大值在左子树中是第 \(3\) 大值。

最后一直递归到只有一个数时,那个数就是答案。

int kthmax(int x,int l,int r,int c){
    if(l==r){
        return l;
    }
    int mid=l+r>>1;
    if(c<=t[x<<1|1]) return kthmax(x<<1|1,mid+1,r,c);
    else return kthmax(x<<1,l,mid,c-t[x<<1|1]);
}

int kthmin(int x,int l,int r,int c){
    if(l==r){
        return l;
    }
    int mid=l+r>>1;
    if(c<=t[x<<1]) return kthmin(x<<1,l,mid,c);
    else return kthmin(x<<1|1,mid+1,r,c-t[x<<1]);
}

引导:

权值线段树一般不会单独用,通常是和合并线段树一起用的

合并线段树的博客:My Blog

posted @ 2021-10-27 14:29  Evitagen  阅读(338)  评论(0编辑  收藏  举报