题目描述
给定一个含有 \(n\) 个数的序列 \(a_1,a_2 \dots a_n\) ,需要进行 \(m\) 次操作,每次操作是下面的两种之一:
-
Q l r k
表示查询下标在区间 \([l,r]\) 中的第 \(k\) 小的数 -
C x y
表示将 \(a_x\) 改为 \(y\) 。
\(1 \leq n ,m \leq 10^5 ,1 \leq a_i,y \leq 10^9\) 。
Solution
和 P3332 [ZJOI2013]K大数查询 基本上一样,大部分内容在前面的题解 题解 P3332 [ZJOI2013]K大数查询 已经写过了。
由于只有单点修改,所以本题直接用权值线段树套动态开点单点修改线段树就可以。
对于本题的 C
操作,我们可以看成在 \(x\) 这个位置删掉了一个 \(a_x\) ,又加上了一个 \(y\) 之后的结果。
所以我们可以分成两次操作:
- 在权值线段树内找到权值为 \(a_x\) 的点,然后把一路上的所有点所对应的内层线段树的 \(x\) 这个位置上 \(-1\) 。
- 在权值线段树内找到权值为 \(y\) 的点,然后把一路上的所有点所对应的内层线段树的 \(x\) 这个位置上 \(+1\) 。
同样的,本题需要离散化,所以要先读入所有的操作再进行操作。
两个坑点:
- 和上题不同的是,这题是找 \(k\) 小,而不是 \(k\) 大,所以我们在查询的时候要先看左儿子落在 \([l,r]\) 的数量,如果不足 \(k\) 个就进入右儿子。
- 离散化的时候原数组记得也要离散化。
代码如下:
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
inline int read() {
int num = 0 ,f = 1; char c = getchar();
while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num * f;
}
inline int max(int a ,int b) {return a > b ? a : b;}
inline int min(int a ,int b) {return a < b ? a : b;}
const int N = 1e5 + 5 ,M = N << 1 ,S = N * 17 * 17;
struct Segment1 {
struct node {
int l ,r ,sum;
node (int l = 0 ,int r = 0 ,int sum = 0) : l(l) ,r(r) ,sum(sum) {}
}t[S]; int tot;
Segment1 () : tot(0) {}
inline void update(int now) {
t[now].sum = t[t[now].l].sum + t[t[now].r].sum;
}
inline void modify(int &now ,int l ,int r ,int q ,int k) {
if (!now) now = ++tot;
if (l == r) {t[now].sum += k; return ;}
int mid = (l + r) >> 1;
if (q <= mid) modify(t[now].l ,l ,mid ,q ,k);
else modify(t[now].r ,mid + 1 ,r ,q ,k);
update(now);
}
inline int query(int &now ,int l ,int r ,int ql ,int qr) {
if (!now) return 0;
if (ql <= l && r <= qr) return t[now].sum;
int mid = (l + r) >> 1 ,ans = 0;
if (ql <= mid) ans += query(t[now].l ,l ,mid ,ql ,qr);
if (qr > mid) ans += query(t[now].r ,mid + 1 ,r ,ql ,qr);
return ans;
}
}u;
int n;
struct Segment {
int t[M << 2];
Segment () {memset(t ,0 ,sizeof(t));}
inline void modify(int now ,int l ,int r ,int p ,int q ,int k) {
u.modify(t[now] ,1 ,n ,p ,k);
if (l == r) return ;
int mid = (l + r) >> 1;
if (q <= mid) modify(now << 1 ,l ,mid ,p ,q ,k);
else modify(now << 1 | 1 ,mid + 1 ,r ,p ,q ,k);
}
inline int query(int now ,int l ,int r ,int ql ,int qr ,int k) {
if (l == r) return l;
int mid = (l + r) >> 1 ,res = u.query(t[now << 1] ,1 ,n ,ql ,qr);
if (res >= k) return query(now << 1 ,l ,mid ,ql ,qr ,k); //先判断左儿子
return query(now << 1 | 1 ,mid + 1 ,r ,ql ,qr ,k - res);
}
}t;
int m ,nums[M] ,tot;
inline int find(int val) {
return lower_bound(nums + 1 ,nums + tot + 1 ,val) - nums;
}
struct Query {
int opt ,x ,y ,k;
Query (int opt = 0 ,int x = 0 ,int y = 0 ,int k = 0) :
opt(opt) ,x(x) ,y(y) ,k(k) {}
}q[N];
char opt[3];
int a[N];
signed main() {
n = read(); m = read();
for (int i = 1; i <= n; i++)
a[i] = read() ,nums[++tot] = a[i];
for (int i = 1; i <= m; i++) {
scanf("%s" ,opt); int x = read() ,y = read();
if (opt[0] == 'Q') {
int k = read();
q[i] = Query(2 ,x ,y ,k);
}
else {
nums[++tot] = y;
q[i] = Query(1 ,x ,y);
}
}
sort(nums + 1 ,nums + tot + 1);
tot = unique(nums + 1 ,nums + tot + 1) - nums - 1;
for (int i = 1; i <= n; i++) { //记得把 a[i] 离散化
a[i] = find(a[i]);
t.modify(1 ,1 ,tot ,i ,a[i] ,1);
}
for (int i = 1; i <= m; i++) {
if (q[i].opt == 2)
printf("%d\n" ,nums[t.query(1 ,1 ,tot ,q[i].x ,q[i].y ,q[i].k)]);
else {
int c = find(q[i].y);
t.modify(1 ,1 ,tot ,q[i].x ,a[q[i].x] ,-1);
t.modify(1 ,1 ,tot ,q[i].x ,c ,1);
a[q[i].x] = c;
}
}
return 0;
}