动态排名 / Dynamic Rankings
题目大意
要你支持可修改的区间第 k 小。
思路
看到题目,情不自禁地想到了可插入可修改的区间第 k 小(什么直接上替罪羊套线段树)。
但是很明显没有插入操作,可以不用平衡树。
那重新搞,先看没有修改,那就是直接一个主席树就完事。
那如果多了修改,我们考虑到修改一个点 x 会对 x∼n 的线段树都会产生影响,显然你要一个一个修改,时间就炸了。
那你考虑到前缀和的查询是 O(1),非常优秀,但它有着修改 O(n) 的确定。
那我们考虑用另一个数据结构来代替,使得它查询和修改都能比较小(比如 O(1),O(√n),O(logn))
然后你就想到了查询和修改都是 O(logn) 的树状数组。
那我们就把外面的前缀和改成树状数组,每次要放一个点进去,就树状数组找它影响的位置,然后在这些位置对于的线段树中这个位置放这个数。
然后查询就是先树状数组找出影响着两个位置的线段树。(因为我们还是要用前缀和来统计,所以找的是 x−1 和 y)
然后就在树上二分,每次所有位置全部往左 / 往右走。
(这个其实就是带修主席树,不过理解成树状数组套线段树也可以)
然后修改操作就相当于在这个位置中把之前的数删掉,然后再放入你现在的数。
那就分解成两个前面一样的操作了。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct Tree {
int l, r, val;
}t[100001 << 8];
int D, n, m, rt[100001], tot, x[100001], y[100001], z[100001];
int a[100001], b[200001], ts[2][30];
char op[100001];
int read() {
int re = 0;
char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re;
}
void insert(int &now, int l, int r, int pl, int num) {
if (!now) now = ++tot, t[now] = (Tree){0, 0, 0};
t[now].val += num;
if (l != r) {
int mid = (l + r) >> 1;
if (pl <= mid) insert(t[now].l, l, mid, pl, num);
else insert(t[now].r, mid + 1, r, pl, num);
}
}
void insert_(int x, int num) {
int pl = lower_bound(b + 1, b + b[0] + 1, a[x]) - b;
for (; x <= n; x += x & (-x)) insert(rt[x], 1, b[0], pl, num);
}
int query(int l, int r, int rnk) {
if (l == r) return l;
int mid = (l + r) >> 1;
int re = 0;
for (int i = 1; i <= ts[1][0]; i++)
re += t[t[ts[1][i]].l].val;
for (int i = 1; i <= ts[0][0]; i++)
re -= t[t[ts[0][i]].l].val;
if (re >= rnk) {
for (int i = 1; i <= ts[1][0]; i++)
ts[1][i] = t[ts[1][i]].l;
for (int i = 1; i <= ts[0][0]; i++)
ts[0][i] = t[ts[0][i]].l;
return query(l, mid, rnk);
}
else {
for (int i = 1; i <= ts[1][0]; i++)
ts[1][i] = t[ts[1][i]].r;
for (int i = 1; i <= ts[0][0]; i++)
ts[0][i] = t[ts[0][i]].r;
return query(mid + 1, r, rnk - re);
}
}
int query_(int x, int y, int z) {
memset(ts, 0, sizeof(ts));
for (int xx = x - 1; xx; xx -= xx & (-xx)) ts[0][++ts[0][0]] = rt[xx];
for (int yy = y; yy; yy -= yy & (-yy)) ts[1][++ts[1][0]] = rt[yy];
return b[query(1, b[0], z)];
}
int main() {
n = read(); m = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
b[++b[0]] = a[i];
}
for (int i = 1; i <= m; i++) {
op[i] = getchar();
while (op[i] != 'Q' && op[i] != 'C') op[i] = getchar();
if (op[i] == 'Q') x[i] = read(), y[i] = read(), z[i] = read();
else x[i] = read(), y[i] = read(), b[++b[0]] = y[i];
}
sort(b + 1, b + b[0] + 1);
b[0] = unique(b + 1, b + b[0] + 1) - b - 1;
for (int i = 1; i <= n; i++)
insert_(i, 1);
for (int i = 1; i <= m; i++) {
if (op[i] == 'C') {
insert_(x[i], -1);
a[x[i]] = y[i];
insert_(x[i], 1);
}
else {
printf("%d\n", query_(x[i], y[i], z[i]));
}
}
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现