主席树

主席树,又叫可持久化权值线段树,其中的每一棵树都是权值线段树。

所谓的权值线段树,就是指线段树的叶子节点保存的是当前值的个数。

以下是线段树的经典问题,求区间第k大。

洛谷P3834:https://www.luogu.org/problemnew/show/P3834

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

const int maxn = 2e5+5;

struct node {
    int l, r, s;    //l是存取左子树l在数组tree中的位置
                    //r是存取右子树r在数组tree中的位置
                    //s则是当前区间中存在值的数量
}tree[maxn * 40];

vector<int> v;    //用来存取离散化的数组

int a[maxn];    
int cnt;
int root[maxn];    //root是用来存取每每颗树的根节点

//返回当前值离散化后的值
int getId(int x) {
    return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;
}

//l,r是区间左右端点,x是前一个树的根节点,y是当前树的根节点,pos是需要添加的值,
//其中&y是为了更新根节点中的左子树或右子树的值,确定他们在tree中所在的位置
void update(int l, int r, int x, int &y, int pos) {
    y = ++cnt;
    tree[y] = tree[x];    //copy前一个树
    tree[y].s++;    //将这个树上pos所在的所有区间值加1
    if(l == r) {
        return;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) {
        update(l, mid, tree[x].l, tree[y].l, pos);
    } else {
        update(mid + 1, r, tree[x].r, tree[y].r, pos);
    }
}

//l,r是区间左右端点,x表示区间[1, left - 1],y表示区间[1, right],num是所求区间内第几大
int query(int l, int r, int x, int y, int num) {
    if(l == r) {
        return l;
    }
    int mid = (l + r) >> 1;
    int sum = tree[tree[y].l].s - tree[tree[x].l].s;     //将两个区间的左子树相减算出区间[left, right]中左子树的范围
    //下面是判断num在左子数还是右子树里面
    if(sum >= num) {
        return query(l, mid, tree[x].l, tree[y].l, num);
    } else {
        return query(mid + 1, r, tree[x].r, tree[y].r, num - sum);    //此处千万要切记num - sum,右子树里并不包含左子树里的结点
    }
}

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        v.push_back(a[i]);
    }
    sort(v.begin(), v.end());
    v.erase(unique(v.begin(), v.end()), v.end());    //离散化操作
    int ed = v.size();    //获取最大的区间长度
    for(int i = 1; i <= n; i++) {
        update(1, ed, root[i - 1], root[i], getId(a[i]));
    }
    while(m--) {
        int left, right, k;
        scanf("%d %d %d", &left, &right, &k);
        int pos = query(1, ed, root[left - 1], root[right], k);
        cout << v[pos - 1] << endl;    //还原离散化
    }
    return 0;
}

 

posted @ 2019-07-23 14:34  不会fly的pig  阅读(205)  评论(0编辑  收藏  举报