本文主要讨论用 树状数组套权值线段树 解决 带修改的区间排名 的问题。

问题

给定序列,要求支持以下操作:

  • 给定区间 $[l, r)$,求该区间内排名为 $k$ 的元素
  • 修改某一位上的数

首先声明

如果没有修改的话,可以用主席树解决,参阅 主席树 - 学习笔记

在网上的很多文章,虽然写着用 树状数组套主席树 解决,然而他们讲的和写的,根本不是什么 树状数组套主席树 ,而是 树状数组套权值线段树 ,跟主席树没有关系,跟可持久化也没有关系!!!

如果非要用到主席树,可以用对原序列建主席树,然后用树状数组套权值线段树维护增量,可是我不觉得这有什么意义啦。

所以以下说的和主席树没有关系。

关于权值线段树,也可以参阅上面那篇文章。

思路

就是一个树套树的通常思路,外层树状数组维护前缀,内层权值线段树求排名为 $k$ 的元素。

对比一般的树状数组来理解,如果说一般的树状数组维护的是某个前缀内元素的和,那么这里的树状数组维护的就是某个前缀内元素构成的权值线段树,原来是把 $O(\log n)$ 个区间的和加起来,现在就是把 $O(\log n)$ 个区间的权值线段树加起来,原来的修改会影响树状数组上 $O(\log n)$ 个区间的和,现在的修改就是在 $O(\log n)$ 颗权值线段树中插入元素。

所以我们可以用树状数组求出前缀的权值线段树,然后将两个前缀树相减,即可得到所需区间的权值线段树,这里和用主席树解决静态问题时的做法类似。

时间复杂度修改和查询都是 $O(\log n\log w)$ 的,空间复杂度是 $O(m\log n\log w)$ 的。

例题

BZOJ 1901 Dynamic Rankings

模板题。

因为我这种存孩子指针和区间线段树的写法空间较大,所以实现上还要注意一些空间问题。

主要措施一个是把动态开点的左右分开,另一个是在不需要往下走,但需要其信息的,直接写成空的信息(本题中即 cnt = 0),而不需要开点。

#include <cstdio>
#include <vector>
#include <algorithm>
#include <climits>
  
const int MAXN = 10000;
const int MAXW = 1e9;
  
struct SegmentTree{
    SegmentTree *lc, *rc;
    int l, r;
    int cnt;
 
    SegmentTree(int l = 0, int r = 0) : l(l), r(r), lc(NULL), rc(NULL), cnt(0) {
        ;
    }
 
    void pushDownL();
    void pushDownR();
    void update();
    void insert(int x, int delta);
};
 
void SegmentTree::pushDownL(){
    if(!lc){
        lc = new SegmentTree(l,  l + (r - l >> 1));
    }
}
 
void SegmentTree::pushDownR(){
    if(!rc){
        rc = new SegmentTree(l + (r - l >> 1), r);
    }
}
 
void SegmentTree::update(){
    cnt = (lc ? lc->cnt : 0) + (rc ? rc->cnt : 0);
}
 
void SegmentTree::insert(int x, int delta){
    if(r - l == 1){
        cnt += delta;
    } else{
        int mid = l + (r - l >> 1);
        if(x < mid) pushDownL(), lc->insert(x, delta); else pushDownR(), rc->insert(x, delta);
        update();
    }
}
 
struct PresentTree{
    int a[MAXN + 1];
    int n;
    SegmentTree *T[MAXN + 1];
    std::vector<SegmentTree*> L, R; // b - a
 
    int lowbit(signed int x){
        return x & -x;
    }
 
    int query(int k){
        int l = T[0]->l, r = T[0]->r;
        while(r - l != 1){
            int cnt = 0;
            for(int i = 0; i < L.size(); i++) cnt -= L[i]->lc ? L[i]->lc->cnt : 0;
            for(int i = 0; i < R.size(); i++) cnt += R[i]->lc ? R[i]->lc->cnt : 0;
  
            int mid = l + (r - l >> 1);
            if(k <= cnt){
                r = mid;
                for(int i = 0; i < L.size(); i++) L[i]->pushDownL(), L[i] = L[i]->lc;
                for(int i = 0; i < R.size(); i++) R[i]->pushDownL(), R[i] = R[i]->lc;
            } else{
                l = mid;
                for(int i = 0; i < L.size(); i++) L[i]->pushDownR(), L[i] = L[i]->rc;
                for(int i = 0; i < R.size(); i++) R[i]->pushDownR(), R[i] = R[i]->rc;
                k -= cnt;
            }
        }
        return l;
    }
 
    int query(int l, int r, int k){ // (l, r]
        L.clear(), R.clear();
        for(int i = l; i; i -= lowbit(i)) L.push_back(T[i]);
        for(int i = r; i; i -= lowbit(i)) R.push_back(T[i]);
        return query(k);
    }
 
    void modify(int i, int x){
        int &old = a[i];
        for(; i <= n; i += lowbit(i)){
            T[i]->insert(old, -1);
            T[i]->insert(x, 1);
        }
        old = x;
    }
 
    void build(){
        for(int i = 0; i <= n; i++) T[i] = new SegmentTree(0, MAXW + 1);
        for(int i = 1; i <= n; i++){
            for(int k = i; k <= n; k += lowbit(k)){
                T[k]->insert(a[i], 1);
            }
        }
    }
} solver;
 
int main(){
    int m;
    scanf("%d%d", &solver.n, &m);
    for(int i = 1; i <= solver.n; i++) scanf("%d", solver.a + i);
 
    solver.build();
 
    for(int i = 0; i < m; i++){
        char opt;
        do opt = getchar(); while(opt != 'C' && opt != 'Q');
 
        if(opt == 'Q'){
            int l, r, k;
            scanf("%d%d%d", &l, &r, &k);
            printf("%d\n", solver.query(l - 1, r, k));
        } else if(opt == 'C'){
            int pos, x;
            scanf("%d%d", &pos, &x);
            solver.modify(pos, x);
        } else throw;
    }
     
    return 0;
}

就是这样啦