【ACWing】2488. 树套树-简单版

题目地址:

https://www.acwing.com/problem/content/description/2490/

请你写出一种数据结构,来维护一个长度为 n n n的序列,其中需要提供以下操作:
1 pos x,将 p o s pos pos位置的数修改为 x x x
2 l r x,查询整数 x x x在区间 [ l , r ] [l,r] [l,r]内的前驱(前驱定义为小于 x x x,且最大的数)。数列中的位置从左到右依次标号为 1 ∼ n 1∼n 1n。区间 [ l , r ] [l,r] [l,r]表示从位置 l l l到位置 r r r之间(包括两端点)的所有数字。区间内排名为 k k k的值指区间内从小到大排在第 k k k位的数值。(位次从 1 1 1开始)

输入格式:
第一行包含两个整数 n , m n,m n,m,表示数列长度以及操作次数。
第二行包含 n n n个整数,表示有序数列。接下来 m m m行,每行包含一个操作指令,格式如题目所述。

输出格式:
对于所有操作 2 2 2,每个操作输出一个查询结果,每个结果占一行。

数据范围:
1 ≤ n , m ≤ 5 × 1 0 4 1≤n,m≤5×10^4 1n,m5×104
1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1lrn
1 ≤ p o s ≤ n 1≤pos≤n 1posn
0 ≤ x ≤ 1 0 8 0≤x≤10^8 0x108
有序数列中的数字始终满足在 [ 0 , 1 0 8 ] [0,10^8] [0,108]范围内,数据保证所有操作一定合法,所有查询一定有解。

可以用树套树来做。为了快速查找 x x x [ l , r ] [l,r] [l,r]的前驱,我们需要平衡树,可以利用线段树将这个区间分为 O ( log ⁡ n ) O(\log n) O(logn)级别个小区间,每个区间维护都维护一个平衡树,这样查找 x x x的前驱就可以在每个小区间里查找前驱,然后对这些前驱求最大值。修改的话可以按照线段树的方式修改。代码如下:

#include <iostream>
#include <set>
using namespace std;

const int N = 5e4 + 10, INF = 1e9;
int n, m;
struct Tree {
    
    
    int l, r;
    multiset<int> s;
} tr[N << 2];
int w[N];

void build(int u, int l, int r) {
    
    
    tr[u] = {
    
    l, r};
    tr[u].s.insert(-INF), tr[u].s.insert(INF);
    for (int i = l; i <= r; i++) tr[u].s.insert(w[i]);
    if (l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

void change(int u, int p, int x) {
    
    
	// 不能写tr[u].s.erase(w[p]),这样会把w[p]的所有出现都删掉的
    tr[u].s.erase(tr[u].s.find(w[p]));
    tr[u].s.insert(x);
    if (tr[u].l == tr[u].r) return;
    
    int mid = tr[u].l + tr[u].r >> 1;
    if (p <= mid) change(u << 1, p, x);
    else change(u << 1 | 1, p, x);
}

int query(int u, int a, int b, int x) {
    
    
    if (a <= tr[u].l && tr[u].r <= b) {
    
    
    	// 找大于等于x的最小值,向前找一位就是x的前驱
        auto it = tr[u].s.lower_bound(x);
        it--;
        return *it;
    }

    int mid = tr[u].l + tr[u].r >> 1, res = -INF;
    if (a <= mid) res = max(res, query(u << 1, a, b, x));
    if (b > mid) res = max(res, query(u << 1 | 1, a, b, x));
    return res;
}

int main() {
    
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
    build(1, 1, n);
    while (m--) {
    
    
        int op, a, b, x;
        scanf("%d", &op);
        if (op == 1) {
    
    
            scanf("%d%d", &a, &x);
            change(1, a, x);
            w[a] = x;
        } else {
    
    
            scanf("%d%d%d", &a, &b, &x);
            printf("%d\n", query(1, a, b, x));
        }
    }

    return 0;
}

每次操作时间复杂度 O ( log ⁡ 2 n ) O(\log^2n) O(log2n),空间 O ( n log ⁡ n ) O(n\log n) O(nlogn)

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/121579396
今日推荐