题面简介:给一个长度为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;
}