(指针主席树简单介绍)第k小数

题面简介:给一个长度为n((n<=2e5))数组序列((|ai|<=1e9)),有m((m<=2e5))次查询,每次查询区间([l,r])中第k小的数。

还是指针香,虽然常数大了一点,但实现起来速度能快不少

前置知识:知道数组主席树的写法,或者差不多知道主席树的原理。需要会一点指针。

主席树,即可持久化线段树,可以记录线段树的多个历史版本,来达到快速查询历史信息的数据结构。

在第k小数中,以给定数组中每个数字下标建立更新权值线段树,如果不使用主席树进行更新并记录历史版本的话,空间将会是n*size(tree)的,即n棵大小为size(tree)的线段树。

主席树大幅减少了建立的节点数,每个版本通过新建一整条分支,分支上的节点和之前的版本共用没有更新的节点,解决了重复的节点建立。

函数详解:

struct tree {
    tree* ls, * rs;
    int cnt;
    tree() {
        ls = rs = NULL;
        cnt = 0;
    }
};
//开内存池
tree* root[maxn];
tree* pool = (tree*)malloc(sizeof(tree) * (21 * maxn));
tree* cnt = pool;

首先是主席树节点。指针实现的常数大,写法较数组写法简单一些,用malloc开内存池比动态开点优化了一些内存和时间。

tree* build(int l, int r)
{
    tree* node = ++cnt;
    if (l == r)return node;
    int mid = l + r >> 1;
    node->ls = build(l, mid);
    node->rs = build(mid + 1, r);
    return node;
}

然后是build函数,将区间分治并构造出线段树,当l==r时返回节点指针。由于节点中没有存储其所包含的区间,所以在之后的查找中需要推算得到l和r。

tree* insert(tree*& p, int l, int r, int x)
{
    tree* q = ++cnt;//新建一个点
    *q = *p;//暂时现将p的数据复制上去,即q的ls、rs也指向p的ls、rs,之后q的ls或rs将会被更新成后继新建的节点
    if (l == r)//权值线段树的更新操作
    {
        q->cnt++;
        return q;//返回节点指针
    }
    int mid = l + r >> 1;
    if (x <= mid)q->ls = insert(p->ls, l, mid, x);//x<=mid,说明需要往左区间更新,右节点代表的右区间不需要更新
    else q->rs = insert(p->rs, mid + 1, r, x);//反之亦然
    q->cnt = q->ls->cnt + q->rs->cnt;//传递更新结果
    return q;//返回节点指针
}

insert应该算是主席树的核心代码,其作用是插入更新结果,并记录历史版本

int query(tree*& q, tree*& p, int l, int r, int k)//历史版本q和历史版本p,这里查询的是区间(l,r),所以历史版本为l-1和r,然后把2个版本作差
{
    if (l == r)return r;//二分的最终答案
    int cnt = q->ls->cnt - p->ls->cnt;//这里是左儿子之间的cnt作差!!!!!!!!!!!!!!!!!!!!!
    int mid = l + r >> 1;
    if (k <= cnt)return query(q->ls, p->ls, l, mid, k);//左儿子里面的数个数大于k,那就向左缩小范围
    else return query(q->rs, p->rs, mid + 1, r, k - cnt);//反之左边的个数没有到k,由于是找第k小数,那么现在的子查询就是去右区间找第k-cnt小数
}

query注释比较详细,需要注意的是每次的cnt都是左儿子的cnt之差

#include<bits/stdc++.h>
#include<unordered_map>
#define ll long long
#define fastio ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
double pi = acos(-1);

const double eps = 1e-6;
const int maxn = 2e5 + 10;
const ll inf = LLONG_MAX;

unordered_map<int, int>id;
int n, m;

struct tree {
    tree* ls, * rs;
    int cnt;
    tree() {
        ls = rs = NULL;
        cnt = 0;
    }
};

//开内存池
tree* root[maxn];
tree* pool = (tree*)malloc(sizeof(tree) * (21 * maxn));
tree* cnt = pool;

int a[maxn];

tree* build(int l, int r)
{
    tree* node = ++cnt;
    if (l == r)return node;
    int mid = l + r >> 1;
    node->ls = build(l, mid);
    node->rs = build(mid + 1, r);
    return node;
}

tree* insert(tree*& p, int l, int r, int x)
{
    tree* q = ++cnt;
    *q = *p;
    if (l == r)
    {
        q->cnt++;
        return q;
    }
    int mid = l + r >> 1;
    if (x <= mid)q->ls = insert(p->ls, l, mid, x);
    else q->rs = insert(p->rs, mid + 1, r, x);
    q->cnt = q->ls->cnt + q->rs->cnt;
    return q;
}

int query(tree*& q, tree*& p, int l, int r, int k)
{
    if (l == r)return r;
    int cnt = q->ls->cnt - p->ls->cnt;
    int mid = l + r >> 1;
    if (k <= cnt)return query(q->ls, p->ls, l, mid, k);
    else return query(q->rs, p->rs, mid + 1, r, k - cnt);
}

int main()
{
    fastio;
    cin >> n >> m;
    vector<int>num;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        num.push_back(a[i]);
    }
    sort(num.begin(), num.end());
    num.erase(unique(num.begin(), num.end()), num.end());

    for (int i = 0; i < num.size(); i++)
        id[num[i]] = i;
    root[0] = build(0, num.size() - 1);
    for (int i = 1; i <= n; i++)
        root[i] = insert(root[i - 1], 0, num.size() - 1, id[a[i]]);

    while (m--)
    {
        int l, r, k;
        cin >> l >> r >> k;
        cout << num[query(root[r], root[l - 1], 0, num.size() - 1, k)] << endl;
    }
    return 0;

}
原文地址:https://www.cnblogs.com/ruanbaiQAQ/p/14119700.html