【序列操作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
输出
3
4
题目分析
本题有多种解法。这里先讲一下较为简单的树状数组套线段树。
外层树状数组n个节点分别对应下标,每个节点其实是一个权值为下标的线段树的根节点(一共有n颗),那么树状数组中的i号点表示的就是区间为1~i的元素,而其下的权值线段树则记录在该区间的权值出现次数。
由于树状数组有前缀和的性质,插入时会更新它及其以后的所有树,询问时也能访问它及其以前的所有树,所以要查询L~R的k小,就只需要将有关区间的点放入一个数组,然后分别进入左子树、右子树查询即可。
看看代码很好理解。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | #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' ); } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步