题目描述
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
1.
1.
1.查询
k
k
k在区间内的排名
2.
2.
2.查询区间内排名为
k
k
k的值
3.
3.
3.修改某一位值上的数值
4.
4.
4.查询
k
k
k在区间内的前驱(前驱定义为小于
x
x
x,且最大的数)
5.
5.
5.查询
k
k
k在区间内的后继(后继定义为大于
x
x
x,且最小的数)
输入格式
第一行两个数
n
,
m
n,m
n,m 表示长度为
n
n
n的有序序列和
m
m
m个操作
第二行有
n
n
n个数,表示有序序列
下面有
m
m
m行,
o
p
t
opt
opt表示操作标号
若
o
p
t
=
1
opt=1
opt=1 则为操作
1
1
1,之后有三个数
l
,
r
,
k
l,r,k
l,r,k 表示查询
k
k
k在区间
[
l
,
r
]
[l,r]
[l,r]的排名
若
o
p
t
=
2
opt=2
opt=2 则为操作
2
2
2,之后有三个数
l
,
r
,
k
l,r,k
l,r,k 表示查询区间
[
l
,
r
]
[l,r]
[l,r]内排名为
k
k
k的数
若
o
p
t
=
3
opt=3
opt=3 则为操作
3
3
3,之后有两个数
p
o
s
,
k
pos,k
pos,k 表示将
p
o
s
pos
pos位置的数修改为
k
k
k
若
o
p
t
=
4
opt=4
opt=4 则为操作
4
4
4,之后有三个数
l
,
r
,
k
l,r,k
l,r,k 表示查询区间
[
l
,
r
]
[l,r]
[l,r]内
k
k
k的前驱
若
o
p
t
=
5
opt=5
opt=5 则为操作
5
5
5,之后有三个数
l
,
r
,
k
l,r,k
l,r,k 表示查询区间
[
l
,
r
]
[l,r]
[l,r]内
k
k
k的后继
输出格式
对于操作 1 , 2 , 4 , 5 1,2,4,5 1,2,4,5各输出一行,表示查询结果
样例输入
6
6
6
4
4
4
2
2
2
2
2
2
1
1
1
9
9
9
4
4
4
0
0
0
1
1
1
1
1
1
2
2
2
1
1
1
4
4
4
3
3
3
3
3
3
4
4
4
10
10
10
2
2
2
1
1
1
4
4
4
3
3
3
1
1
1
2
2
2
5
5
5
9
9
9
4
4
4
3
3
3
9
9
9
5
5
5
5
5
5
2
2
2
8
8
8
5
5
5
样例输出
2
2
2
4
4
4
3
3
3
4
4
4
9
9
9
提示
1.
1.
1.
n
n
n和
m
m
m的数据范围:
n
,
m
≤
50000
n,m≤50000
n,m≤50000
2.
2.
2.序列中每个数的数据范围:
[
0
,
1
e
8
]
[0,1e8]
[0,1e8]
3.
3.
3.虽然原题没有,但事实上
5
5
5操作的
k
k
k可能为负数
前言:
线段树通常用来解决区间上的问题,而平衡树可以支持查询排名、前驱、后继、第
k
k
k小等问题。
如果要支持动态的区间上的上述问题,就必须用树套树这种神奇的数据结构。(可能还有别的做法吧,但本蒟蒻只会线段树套平衡树)
具体来说,用线段树对原序列进行划分,每个节点上开一颗平衡树,用来维护该段区间元素权值的信息。
举个栗子:
设原序列是
4
4
4
2
2
2
3
3
3
1
1
1
9
9
9
4
4
4
5
5
5
1
1
1
那么将原序列构造一颗线段树,如下图所示
如果要在第
2
2
2~
7
7
7个元素中求
7
7
7的后继,那么我们就可以在
[
2
,
2
]
,
[
3
,
4
]
,
[
5
,
6
]
,
[
7
,
7
]
[2,2],[3,4],[5,6],[7,7]
[2,2],[3,4],[5,6],[7,7]中分别求出
7
7
7的后继为
+
∞
,
+
∞
,
9
,
+
∞
+∞,+∞,9,+∞
+∞,+∞,9,+∞,对这些值取
m
i
n
min
min即可。
那么求前驱、排名也是类似的
关于如何求第
k
k
k小数,我们可以二分答案, 求最大的
x
x
x,使得在
[
l
,
r
]
[l,r]
[l,r]中比
x
x
x小的数的个数为
k
−
1
k-1
k−1
时空复杂度分析:
- 空间复杂度:
线段树的树高是 O ( log n ) O(\log n) O(logn),而线段树上每一层区间的并集都是 n n n,即每一层的空间复杂度是 O ( n ) O(n) O(n)的,所以总复杂度为 O ( n log n ) O(n \log n) O(nlogn) - 时间复杂度
对于求前驱、后继和排名,原询问区间会在线段树上划分成 O ( log n ) O(\log n) O(logn)段区间,在每段区间中查询信息时间复杂度是 O ( log n ) O(\log n) O(logn)的,所以总时间复杂度为 O ( log 2 n ) O(\log^2 n) O(log2n)
对于求第 k k k小,因为需要二分答案,所以在配合对原序列和修改的值离散化的情况下可以做到 O ( log 3 n ) O(\log^3 n) O(log3n)(当然本人很懒,而且此题对常数没有太大的要求,就没有离散化)
代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 50005;
const int maxq = 10005;
const int maxt = 2000005;
const int oo = 2147483647;
int a[maxn];
int max(int x, int y) {return x > y ? x : y;}
int min(int x, int y) {return x < y ? x : y;}
int read() {
char ch = getchar(); bool f = 1;
while(ch < '0' || ch > '9') f &= ch != '-', ch = getchar();
int res = 0;
while(ch >= '0' && ch <= '9') res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return f ? res : -res;
}
void write(int x) {
if(x < 0) x = -x, putchar('-');
int len = 0, res[15];
for(; x; res[++len] = x % 10, x /= 10);
for(int i = len; i >= 1; i--) putchar(res[i] + 48);
if(!len) putchar('0');
}
class Treap {
private:
static int cnt;
static struct node {int son[2], val, pri, cnt, siz;} t[maxt];
inline int build(int val) {
t[++cnt] = (node) {{0, 0}, val, rand(), 1, 1};
return cnt;
}
inline void update(int p) {t[p].siz = t[t[p].son[0]].siz + t[p].cnt + t[t[p].son[1]].siz;}
void rotate(int &p, bool d) {
int k = t[p].son[d];
t[p].son[d] = t[k].son[d ^ 1];
t[k].son[d ^ 1] = p;
update(p); update(p = k);
}
public:
int rt;
inline void clear() {rt = 0;}
void insert(int &p, int val) {
if(!p) {p = build(val); return;}
t[p].siz++;
if(t[p].val == val) {t[p].cnt++; return;}
bool d = t[p].val < val;
insert(t[p].son[d], val);
if(t[p].pri > t[t[p].son[d]].pri) rotate(p, d);
}
void remove(int &p, int val) {
if(t[p].val == val) {
if(t[p].cnt > 1) {t[p].cnt--; t[p].siz--; return;}
if(!t[p].son[0] || !t[p].son[1]) {p = t[p].son[0] + t[p].son[1]; return;}
bool d = t[t[p].son[0]].pri > t[t[p].son[1]].pri;
rotate(p, d); remove(p, val);
return;
}
t[p].siz--;
bool d = t[p].val < val;
remove(t[p].son[d], val);
}
int lower(int p, int val) {
if(!p) return 0;
if(t[p].val > val) return lower(t[p].son[0], val);
if(t[p].val == val) return t[t[p].son[0]].siz;
return t[t[p].son[0]].siz + t[p].cnt + lower(t[p].son[1], val);
}
int kth(int p, int rnk) {
if(!p) return 0;
if(t[t[p].son[0]].siz >= rnk) return kth(t[p].son[0], rnk);
if(t[t[p].son[0]].siz + t[p].cnt >= rnk) return t[p].val;
return kth(t[p].son[1], rnk - t[t[p].son[0]].siz - t[p].cnt);
}
int pre(int p, int val) {
if(!p) return -oo;
if(t[p].val >= val) return pre(t[p].son[0], val);
return max(t[p].val, pre(t[p].son[1], val));
}
int suc(int p, int val) {
if(!p) return oo;
if(t[p].val <= val) return suc(t[p].son[1], val);
return min(t[p].val, suc(t[p].son[0], val));
}
} sgt[maxn << 2];
int Treap::cnt = 0;
Treap::node Treap::t[maxt];
void build(int p, int l, int r) {
sgt[p].clear();
for(int i = l; i <= r; i++) sgt[p].insert(sgt[p].rt, a[i]);
if(l == r) return;
int mid = l + r >> 1;
build(p + p, l, mid);
build(p + p + 1, mid + 1, r);
}
int query_lower(int p, int l, int r, int x, int y, int k) {
if(l == x && r == y) return sgt[p].lower(sgt[p].rt, k);
int mid = l + r >> 1;
if(y <= mid) return query_lower(p + p, l, mid, x, y, k);
else if(x > mid) return query_lower(p + p + 1, mid + 1, r, x, y, k);
else return query_lower(p + p, l, mid, x, mid, k) + query_lower(p + p + 1, mid + 1, r, mid + 1, y, k);
}
int query_pre(int p, int l, int r, int x, int y, int k) {
if(l == x && r == y) return sgt[p].pre(sgt[p].rt, k);
int mid = l + r >> 1;
if(y <= mid) return query_pre(p + p, l, mid, x, y, k);
else if(x > mid) return query_pre(p + p + 1, mid + 1, r , x, y, k);
else return max(query_pre(p + p, l, mid, x, mid, k), query_pre(p + p + 1, mid + 1, r, mid + 1, y, k));
}
int query_suc(int p, int l, int r, int x, int y, int k) {
if(l == x && r == y) return sgt[p].suc(sgt[p].rt, k);
int mid = l + r >> 1;
if(y <= mid) return query_suc(p + p, l, mid, x, y, k);
else if(x > mid) return query_suc(p + p + 1, mid + 1, r , x, y, k);
else return min(query_suc(p + p, l, mid, x, mid, k), query_suc(p + p + 1, mid + 1, r, mid + 1, y, k));
}
void update(int p, int l, int r, int x, int k) {
sgt[p].remove(sgt[p].rt, a[x]); sgt[p].insert(sgt[p].rt, k);
if(l == r) return;
int mid = l + r >> 1;
if(x <= mid) update(p + p, l, mid, x, k);
else update(p + p + 1, mid + 1, r, x, k);
}
int main() {
int n = read(), q = read();
for(int i = 1; i <= n; i++) a[i] = read();
build(1, 1, n);
while(q--) {
int opt = read();
if(opt == 1) {
int l = read(), r = read(), k = read();
write(query_lower(1, 1, n, l, r, k) + 1), putchar('\n');
}
if(opt == 2) {
int l = read(), r = read(), k = read();
int L = 0, R = 1e8;
while(L < R) {
int mid = L + R + 1 >> 1;
if(query_lower(1, 1, n, l, r, mid) < k) L = mid;
else R = mid - 1;
}
write(R), putchar('\n');
}
if(opt == 3) {
int pos = read(), k = read();
update(1, 1, n, pos, k);
a[pos] = k;
}
if(opt == 4) {
int l = read(), r = read(), k = read();
write(query_pre(1, 1, n, l, r, k)), putchar('\n');
}
if(opt == 5) {
int l = read(), r = read(), k = read();
write(query_suc(1, 1, n, l, r, k)), putchar('\n');
}
}
return 0;
}