You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
6.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
typedef long long LL;
//重定义 tr[u].l 和 tr[u].r 快速录入用
#define ls tr[u].lson
#define rs tr[u].rson
const int N = 200010;
int n, m;
int a[N]; //记录原始数组其实也可以不用保存直接在build时录入到叶子也可以
int root[N], idx; //分裂出的第几个线段树,idx是序号维护器
struct Node {
int lson, rson; //动态开点,记录左右儿子的节点编号
//注意:这里与普通线段树不同,没有记录当前节点的管辖范围!!!!!
//黄海尝试了记录[l,r]的方法结果因为结构体内增加了两个属性同时数组的上限非常大直接MLE了好多测试点。
LL val; //每一个节点保存的值大小是管辖区间中的个数
} tr[N << 6];
int cnt; //记录tr数组中的可以放入节点的位置
//权值线段树合并,维护区间元素个数
void pushup(int u) {
tr[u].val = tr[ls].val + tr[rs].val;
}
/**
* @brief 构建线段树
*
* @param l 想要创建的节点,是管理哪个范围的,这与普通线段树相似,只不过普通线段树可以事先准备好节点号,这个需要现用现创建,
* 并且需要返回节点号,让父节点记录和父节点的关联关系。
* @param r
* @return int
*/
int build(int l, int r) {
//由于构建之前空间是未分配状态需要为新构建的节点分配节点号节点号就是cnt
int u = ++cnt;
//现在有了可用的节点号了
if (l == r) { //如果是叶子节点
//接下来一行n个整数表示 1n 这些数在a[N]中出现的 **次数**
//其实7就放在[7,7]的位置上val就记录7这个数字出现的次数
tr[u].val = a[l]; //个数!!!
return u;
}
int mid = (l + r) >> 1;
//由于是动态开点无法像普通线段树一样采用预先创建的办法普通办法只要知道父节点u<<1 和 u<<1|1就是左右儿子节点号
//而在动态开点的模板中需要递归创建并返回新建节点编号由并记录到tr[u].l和tr[u].r上以实现父子节点的关联对应
ls = build(l, mid), rs = build(mid + 1, r);
//更新父节点统计信息
pushup(u);
return u;
}
/**
* @brief 线段树分裂
*
* @param k 以k为根的线段树
* @param l 左边界
* @param r 右边界
* @param ql 要分裂出的左边界
* @param qr 要分裂出的右边界
* @return int
*/
int spilt(int k, int l, int r, int ql, int qr) {
int u = ++cnt; //分裂后的根节点编号u
if (l == ql && r == qr) { //完全命中区间
tr[u] = tr[k]; //将tr[k]抄到tr[u]
tr[k].val = tr[k].lson = tr[k].rson = 0; //销毁tr[k]
return u; //返回新分裂后的节点编号u
}
int mid = (l + r) >> 1;
if (qr <= mid) //分裂左儿子
ls = spilt(tr[k].lson, l, mid, ql, qr);
else if (ql > mid) //分裂右儿子
rs = spilt(tr[k].rson, mid + 1, r, ql, qr);
else {
ls = spilt(tr[k].lson, l, mid, ql, mid);
rs = spilt(tr[k].rson, mid + 1, r, mid + 1, qr);
}
//递归更新分裂后u和k的父节点信息
pushup(u), pushup(k);
return u;
}
//合并线段树
void merge(int &x, int y) {
if (!(x && y))
x |= y;
else {
tr[x].val += tr[y].val;
merge(tr[x].lson, tr[y].lson);
merge(tr[x].rson, tr[y].rson);
}
}
/**
* @brief 在 u 这个可重集中加入 x 个数字 q
*
* @param u 以u为根
* @param l 左边界
* @param r 右边界
* @param q 数字p
* @param x 增加x个
*/
void insert(int u, int l, int r, int q, int x) {
if (l == r) {
tr[u].val += x; //权值线段树个数增加x个
return;
}
int mid = (l + r) >> 1;
if (q <= mid) {
if (ls == 0) ls = ++cnt; //左儿子不存在,则创建之
insert(ls, l, mid, q, x); //向左儿子递归插入
} else {
if (rs == 0) rs = ++cnt; //右儿子不存在,则创建之
insert(rs, mid + 1, r, q, x); //向右儿子递归插入
}
//向父节点更新统计信息
pushup(u);
}
/**
* @brief 在以u节点为根的线段树中管辖范围是[l,r],查询区间[ql,qr]内数字的总个数
* 查询可重集 p中大于等于 x 且小于等于 y 的值的个数。
* @param u 根节点
* @param l 左边界
* @param r 右边界
* @param ql 查询左边界
* @param qr 查询右边界
* @return LL 有多少个
*/
LL query(int u, int l, int r, int ql, int qr) {
if (l == ql && r == qr) return tr[u].val;
int mid = (l + r) >> 1;
if (qr <= mid)
return query(ls, l, mid, ql, qr);
else if (ql > mid)
return query(rs, mid + 1, r, ql, qr);
else
return query(ls, l, mid, ql, mid) + query(rs, mid + 1, r, mid + 1, qr);
}
//查询第k小的数
int kth(int u, int l, int r, int k) {
if (l == r) return l;
int mid = (l + r) >> 1;
if (tr[ls].val >= k)
return kth(ls, l, mid, k);
return kth(rs, mid + 1, r, k - tr[ls].val);
}
int main() {
//加快读入
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m;
//范围[1~n],创建第一个线段树返回值是根节点号记录到root[1]中
for (int i = 1; i <= n; i++) cin >> a[i];
root[++idx] = build(1, n);
int op, p, x, y;
for (int i = 1; i <= m; i++) {
cin >> op >> p;
if (op == 0) { //将可重集p中大于等于x且小于等于y的值放入一个新的可重集中
//新可重集编号为从2开始的正整数是上一次产生的新可重集的编号+1。
cin >> x >> y;
root[++idx] = spilt(root[p], 1, n, x, y);
} else if (op == 1) { //合并线段树
cin >> x;
merge(root[p], root[x]); //将根为x的线段树合并到根为p的线段树中去,x线段树以后就不用了
} else if (op == 2) { //在p这个可重集中加入x个数字q
cin >> x >> y;
insert(root[p], 1, n, y, x);
} else if (op == 3) { //区间查询
cin >> x >> y;
printf("%lld\n", query(root[p], 1, n, x, y));
} else {
int k;
cin >> k; //在线段树上二分如果左孩子的元素个数大于等于k说明第k小在左子树内
//否则,在右子树内。
if (query(root[p], 1, n, 1, n) < k)
printf("-1\n");
else
printf("%d\n", kth(root[p], 1, n, k));
}
}
return 0;
}