【序列操作IV】树状数组套线段树/树套树

题目描述

给出序列 a1,a2,…,an(0≤ai≤109),有关序列的两种操作。

1. ai(1≤i≤n)变成 x(0≤x≤109)。

2. 求 al,al+1,…,ar(1≤l≤r≤n)第 k(1≤k≤r-l+1)小。

输入格式

第一行包含两个数 n(1≤n≤2×104)和 m(1≤m≤2×104),表示序列长度和操作次数。

接下来一行 n 个数,以空格隔开,表示 a1,a2,…,an 。

接下来m行,每行为以下两种格式之一:

    • 0 i x ,  表示 ai=x。
    • 1 l r k ,求 al,al+1,…,ar 的第 k 小。

输出格式

对于每次询问,输出单独的一行表示答案。

样例数据 1

输入

5 3 
1 2 3 4 5 
1 1 5 3 
0 3 5 
1 1 5 3

输出


4

题目分析

本题有多种解法。这里先讲一下较为简单的树状数组套线段树。

外层树状数组n个节点分别对应下标,每个节点其实是一个权值为下标的线段树的根节点(一共有n颗),那么树状数组中的i号点表示的就是区间为1~i的元素,而其下的权值线段树则记录在该区间的权值出现次数。

由于树状数组有前缀和的性质,插入时会更新它及其以后的所有树,询问时也能访问它及其以前的所有树,所以要查询L~R的k小,就只需要将有关区间的点放入一个数组,然后分别进入左子树、右子树查询即可。

看看代码很好理解。

code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;

const int N = 2e4 + 5, M = 2e6 + 5;
int len, a[N], b[N * 2], root[N], tot;
int cur[N], n, m;
struct node{
    int cnt, lc, rc;
}tr[M];
struct qry{
    int opt, a, b, c;
}Q[N];

inline int read(){
    int i = 0, f = 1; char ch = getchar();
    for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
    if(ch == '-') f = -1, ch = getchar();
    for(; ch >= '0' && ch <= '9'; ch = getchar())
        i = (i << 3) + (i << 1) + (ch - '0');
    return i * f;
}

inline void wr(int x){
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) wr(x / 10);
    putchar(x % 10 + '0');
}

inline void disc_init(){
    sort(b + 1, b + len + 1);
    len = unique(b + 1, b + len + 1) - (b + 1);
    for(int i = 1; i <= n; i++)
        a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
    for(int i = 1; i <= m; i++)
        if(Q[i].opt == 0) Q[i].b = lower_bound(b + 1, b + len + 1, Q[i].b) - b;
}
inline void insert(int &k, int l, int r, int p, int v){
    if(!k) k = ++tot;
    tr[k].cnt += v;
    if(l == r) return;
    int mid = l + r >> 1;
    if(p <= mid) insert(tr[k].lc, l, mid, p, v);
    else insert(tr[k].rc, mid + 1, r, p, v);
}

inline void update(int k, int p, int v){
    for(int i = k; i <= n; i += (i & -i))
        insert(root[i], 1, len, p, v);
}

inline void gotoPos(int lx, int rx){
    for(int i = rx; i > 0; i -= (i & -i)) cur[i] = root[i];
    for(int i = lx; i > 0; i -= (i & -i)) cur[i] = root[i];
}

inline int CntLc(int lx, int rx){
    int ret = 0;
    for(int i = rx; i > 0; i -= (i & -i)) ret += tr[tr[cur[i]].lc].cnt;
    for(int i = lx; i > 0; i -= (i & -i)) ret -= tr[tr[cur[i]].lc].cnt;
    return ret;
}

inline int query(int nl, int nr, int l, int r, int k){
    if(l == r) return l;
    int ret = CntLc(nl, nr), mid = l + r >> 1;
    if(ret >= k){
        for(int i = nr; i > 0; i -= (i & -i)) cur[i] = tr[cur[i]].lc;
        for(int i = nl; i > 0; i -= (i & -i)) cur[i] = tr[cur[i]].lc;
        return query(nl, nr, l, mid, k);
    }
    else{
        for(int i = nr; i > 0; i -= (i & -i)) cur[i] = tr[cur[i]].rc;
        for(int i = nl; i > 0; i -= (i & -i)) cur[i] = tr[cur[i]].rc;
        return query(nl, nr, mid + 1, r, k - ret);
    }
}

inline void tree_init(){
    for(int i = 1; i <= n; i++) update(i, a[i], 1);
}

int main(){
    n = read(), m = read();
    for(int i = 1; i <= n; i++) a[i] = b[++len] = read();
    for(int i = 1; i <= m; i++){
        Q[i].opt = read();
        if(Q[i].opt == 1)
            Q[i].a = read(), Q[i].b = read(), Q[i].c = read();
        else Q[i].a = read(), Q[i].b = b[++len] = read();
    }
    disc_init();
    tree_init();
    for(int i = 1; i <= m; i++){
        if(Q[i].opt == 0){
            update(Q[i].a, a[Q[i].a], -1);
            a[Q[i].a] = Q[i].b;
            update(Q[i].a, Q[i].b, 1);
        }
        else{
            gotoPos(Q[i].a - 1, Q[i].b);
            wr(b[query(Q[i].a - 1, Q[i].b, 1, len, Q[i].c)]), putchar('\n');
        }
    }
}
posted @ 2017-07-27 20:00  CzYoL  阅读(446)  评论(0编辑  收藏  举报