洛谷P3834 【模板】可持久化线段树 2/POJ2104 K-th Number 题解 主席树
题目链接:https://www.luogu.com.cn/problem/P3834
本题同 POJ2104 K-th Number http://poj.org/problem?id=2104
题目大意:\(n\) 个数,\(m\) 次询问。每次询问要求求出区间 \([l,r]\) 范围内第 \(k\) 小的数。
暴力解法
每次取一个区间的副本并排序,然后求区间第 \(k\) 小的那个数。时间复杂度 \(O(n \cdot m \cdot \log n)\)。(50分)
示例程序:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200020;
int n, m, a[maxn], l, r, k, b[maxn];
int main() {
ios::sync_with_stdio(0);
cin >> n >> m;
for (int i = 1; i <= n; i ++)
cin >> a[i];
while (m --) {
cin >> l >> r >> k;
memcpy(b+l, a+l, sizeof(int)*(r-l+1));
sort(b+l, b+r+1);
cout << b[l+k-1] << endl;
}
return 0;
}
主席树
对于原序列的每一个前缀 \([1 \ldots i]\) 建立出一棵线段树维护值域上每个数出现的次数,则这棵树是可减的。
维护一棵权值线段树,每个节点 \(rt\) 保存数值为 \([ lson[rt], rson[rt] ]\) 的数的个数。
对于每次询问的 \([L,R]\) 范围内第 \(k\) 小的数,
设 \(t_1\) 为第 \(L-1\) 个版本的线段树,\(t_2\) 为第 \(R\) 个版本的线段树,则查询第 \(k\) 小的数,若 \(t_2\) 的左子树元素个数 \(\ge t_1 + k\) 的左子树元素个数 \(+ k\),则在左子树;否则,在右子树。
示例代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200020;
vector<int> v;
inline int getid(int x) { return lower_bound(v.begin(), v.end(), x) - v.begin() + 1; }
struct Node {
int l, r, sum;
} tree[maxn*40];
int cnt, root[maxn];
int n, m, a[maxn];
void add(int l, int r, int pre, int &now, int p) {
tree[now = ++cnt] = tree[pre];
tree[now].sum ++;
if (l == r) return;
int mid = (l + r) / 2;
if (p <= mid) add(l, mid, tree[pre].l, tree[now].l, p);
else add(mid+1, r, tree[pre].r, tree[now].r, p);
}
int query(int l, int r, int rt1, int rt2, int k) {
if (l == r) return l;
int mid = (l + r) / 2;
int tmp = tree[ tree[rt2].l ].sum - tree[ tree[rt1].l ].sum;
if (k <= tmp) return query(l, mid, tree[rt1].l, tree[rt2].l, k);
else return query(mid+1, r, tree[rt1].r, tree[rt2].r, k-tmp);
}
int main() {
ios::sync_with_stdio(0);
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
v.push_back(a[i]);
}
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
for (int i = 1; i <= n; i ++) {
add(1, n, root[i-1], root[i], getid(a[i]));
}
while (m --) {
int l, r, k;
cin >> l >> r >> k;
cout << v[query(1, n, root[l-1], root[r], k)-1] << endl;
}
return 0;
}