洛谷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;
}
posted @ 2021-10-26 13:15  quanjun  阅读(45)  评论(0编辑  收藏  举报