主席树
主席树,又叫可持久化权值线段树,其中的每一棵树都是权值线段树。
所谓的权值线段树,就是指线段树的叶子节点保存的是当前值的个数。
以下是线段树的经典问题,求区间第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; }