标签:大小 实现 text print bsp 结果 3.0 位置 space
这是个非常经典的主席树入门题——静态区间第K小
数据已经过加强,请使用主席树。同时请注意常数优化
如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
输入格式:
第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。
第二行包含N个正整数,表示这个序列各项的数字。
接下来M行每行包含三个整数 l,r,k l, r, kl,r,k , 表示查询区间 [l,r][l, r][l,r] 内的第k小值。
输出格式:输出包含k行,每行1个正整数,依次表示每一次查询的结果
5 5 25957 6405 15770 26287 26465 2 2 1 3 4 1 4 5 1 1 2 2 4 4 1
6405 15770 26287 25957 26287
数据范围:
对于20%的数据满足: 1≤N,M≤101 \leq N, M \leq 101≤N,M≤10
对于50%的数据满足: 1≤N,M≤1031 \leq N, M \leq 10^31≤N,M≤103
对于80%的数据满足: 1≤N,M≤1051 \leq N, M \leq 10^51≤N,M≤105
对于100%的数据满足: 1≤N,M≤2⋅1051 \leq N, M \leq 2\cdot 10^51≤N,M≤2⋅105
对于数列中的所有数 aia_iai? ,均满足 −109≤ai≤109-{10}^9 \leq a_i \leq {10}^9−109≤ai?≤109
样例数据说明:
N=5,数列长度为5,数列从第一项开始依次为 [25957,6405,15770,26287,26465][25957, 6405, 15770, 26287, 26465 ][25957,6405,15770,26287,26465]
第一次查询为 [2,2][2, 2][2,2] 区间内的第一小值,即为6405
第二次查询为 [3,4][3, 4][3,4] 区间内的第一小值,即为15770
第三次查询为 [4,5][4, 5][4,5] 区间内的第一小值,即为26287
第四次查询为 [1,2][1, 2][1,2] 区间内的第二小值,即为25957
第五次查询为 [4,4][4, 4][4,4] 区间内的第一小值,即为26287
————————————————————————————————————————————————————————————————————————————————
主席树是个好东西啊
为何我还是如此之菜
干脆直接背板子算了
主席树挺灵活的……还是理解透彻比较好
主席树还是挺好理解的QwQ
#include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <cstdio> #include <iostream> #include <map> #include <queue> #define R register typedef long long ll; typedef double db; const int MAXN = 200010; int N, M, a[MAXN], b[MAXN]; struct PTree{ //主席树第i棵树存的是a[i]-a[0]的树(前缀和) //l表示该节点的左儿子,r表示该节点的右儿子, //id表示当前树的根节点编号,sum表示当前区间所包含的数的个数 int l, r, id, sum; }pt[MAXN*10]; int cnt; inline int read() { int num = 0, f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == ‘-‘) f = -1; ch = getchar(); } while (isdigit(ch)) { num = num * 10 + ch - ‘0‘; ch = getchar(); } return num * f; } void Build (int &now, int l ,int r) { //当前节点没有在前面出现过时,用普通的建树方法 //now在这里代指当前节点编号 //类似与线段树的建树方法,但是主席树不满足线段树的节点与左儿子右儿子的关系 //每次加入只是单纯的按计数器分配给当前节点一个值。 now = ++ cnt; pt[now].sum = 0; if (l == r) return ; int mid = (l + r) >> 1; Build(pt[now].l, l, mid); Build(pt[now].r, mid + 1, r); } void update(int &now, int l, int r, int last, int x) { //为了防止MLE,若当前节点在之前出现过,直接从之前节点那里继承过来 //update是主席树实现可持久化的函数 //last是上个节点的编号 now = ++ cnt; //继承前面节点的儿子 pt[now].l = pt[last].l; pt[now].r = pt[last].r; //继承前面节点的sum并将当前的数加入,相当于sum+1 pt[now].sum = pt[last].sum + 1; if (l == r) return ; int mid = (l + r) >> 1; //如果x<=mid,可知待处理元素在左子树中,右子树可以完全共用,可以直接由祖先节点访问到 //只需要处理左子树即可。反之亦然 if (x <= mid) update(pt[now].l, l, mid, pt[now].l, x); else update(pt[now].r, mid + 1, r, pt[now].r, x); } int query(int s, int t, int l, int r, int x) { //实际上是一个递归二分过程 //pt[i]表示处理完前i个数之后形成的线段树 if (l == r) return l; int mid = (l + r) >> 1; //若当前查询值小于左子树中存的个数,直接继续查询左子树中第x小的数 int cnt = pt[pt[t].l].sum - pt[pt[s].l].sum; if (x <= cnt) return query(pt[s].l, pt[t].l, l, mid, x); //不然就向右子树中查询,显然问题变成在右子树中找x-cnt大的数 else return query(pt[s].r, pt[t].r, mid + 1, r, x - cnt); } int main() { N = read(), M = read(); for (R int i = 1; i <= N; ++ i) a[i] = read(), b[i] = a[i]; //b[i]是a[i]的副本,下面是类似于离散化的东西,确定一共有多少不同的数出现过,确定树的大小 std::sort(b + 1, b + N + 1); int Siz = std::unique(b + 1, b + N + 1) - b - 1; //先将原始的主席树建立出来 Build(pt[0].id, 1, Siz); //用每个元素在b中的坐标重置a数组(离散化) for (R int i = 1; i <= N; ++ i) a[i] = std::lower_bound(b + 1, b + Siz + 1, a[i]) - b; //可持久化操作,对于每个a[i]都往主席树中update for (R int i = 1; i <= N; ++ i) update(pt[i].id, 1, Siz, pt[i - 1].id, a[i]); int x, y, z; while (M -- ) { x = read(), y = read(), z = read(); //其实和一维前缀和很类似(pt[].sum就是前缀和) printf("%d\n", b[query(pt[x - 1].id, pt[y].id, 1, Siz, z)]); } return 0; }
做完才发现还有一道题
UPDATE : 最后一个点时间空间已经放大
标题即题意
有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集)
如题,你需要维护这样的一个长度为 N N N 的数组,支持如下几种操作
在某个历史版本上修改某一个位置上的值
访问某个历史版本上的某一位置的值
此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)
输入的第一行包含两个正整数 N,M N, M N,M , 分别表示数组的长度和操作的个数。
第二行包含 N N N 个整数,依次为初始状态下数组各位的值(依次为 ai a_i ai? , 1≤i≤N 1 \leq i \leq N 1≤i≤N )。
接下来 M M M 行每行包含3或4个整数,代表两种操作之一( i i i 为基于的历史版本号):
对于操作1,格式为 vi 1 loci valuei v_i \ 1 \ {loc}_i \ {value}_i vi? 1 loci? valuei? ,即为在版本 vi v_i vi? 的基础上,将 aloci a_{{loc}_i} aloci?? 修改为 valuei {value}_i valuei?
对于操作2,格式为 vi 2 loci v_i \ 2 \ {loc}_i vi? 2 loci? ,即访问版本 vi v_i vi? 中的 aloci a_{{loc}_i} aloci?? 的值
输出包含若干行,依次为每个操作2的结果。
5 10 59 46 14 87 41 0 2 1 0 1 1 14 0 1 1 57 0 1 1 88 4 2 4 0 2 5 0 2 4 4 2 1 2 2 2 1 1 5 91
59 87 41 87 88 46
数据规模:
对于30%的数据: 1≤N,M≤103 1 \leq N, M \leq {10}^3 1≤N,M≤103
对于50%的数据: 1≤N,M≤104 1 \leq N, M \leq {10}^4 1≤N,M≤104
对于70%的数据: 1≤N,M≤105 1 \leq N, M \leq {10}^5 1≤N,M≤105
对于100%的数据: 1≤N,M≤106,1≤loci≤N,0≤vi<i,−109≤ai,valuei≤109 1 \leq N, M \leq {10}^6, 1 \leq {loc}_i \leq N, 0 \leq v_i < i, -{10}^9 \leq a_i, {value}_i \leq {10}^91≤N,M≤106,1≤loci?≤N,0≤vi?<i,−109≤ai?,valuei?≤109
经测试,正常常数的可持久化数组可以通过,请各位放心
数据略微凶残,请注意常数不要过大
另,此题I/O量较大,如果实在TLE请注意I/O优化
询问生成的版本是指你访问的那个版本的复制
样例说明:
一共11个版本,编号从0-10,依次为:
* 0 : 59 46 14 87 41
* 1 : 59 46 14 87 41
* 2 : 14 46 14 87 41
* 3 : 57 46 14 87 41
* 4 : 88 46 14 87 41
* 5 : 88 46 14 87 41
* 6 : 59 46 14 87 41
* 7 : 59 46 14 87 41
* 8 : 88 46 14 87 41
* 9 : 14 46 14 87 41
* 10 : 59 46 14 87 91
这不就是不考虑区间第k大的主席树吗……
是不是这才是真正的入门主席树模板……我是不是应该先做这个……
管他呢怒A一发
#include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <cstdio> #include <iostream> #include <map> #include <queue> #define R register typedef long long ll; typedef double db; const int MAXN = 1000010; int N, M, a[MAXN], b[MAXN]; struct PTree{ int l, r, id, val; }pt[MAXN*20]; int cnt; inline int read() { int num = 0, f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == ‘-‘) f = -1; ch = getchar(); } while (isdigit(ch)) { num = num * 10 + ch - ‘0‘; ch = getchar(); } return num * f; } void Build (int &now, int l ,int r) { now = ++ cnt; if (l == r) {pt[now].val = a[l]; return ;} int mid = (l + r) >> 1; Build(pt[now].l, l, mid); Build(pt[now].r, mid + 1, r); } void update(int &now, int l, int r, int last, int q, int v) { now = ++ cnt; pt[now].l = pt[last].l; pt[now].r = pt[last].r; pt[now].val = pt[last].val; if (l == r) {pt[now].val = v;return ;} int mid = (l + r) >> 1; if (q <= mid) update(pt[now].l, l, mid, pt[now].l, q, v); else update(pt[now].r, mid + 1, r, pt[now].r, q, v); } int query(int u, int l, int r, int q) { if (l == r) return pt[u].val; int mid = (l + r) >> 1; if (q <= mid) return query(pt[u].l, l, mid, q); else return query(pt[u].r, mid + 1, r, q); } int main() { N = read(), M = read(); for (R int i = 1; i <= N; ++ i) a[i] = read(); Build(pt[0].id, 1, N); int pre, opt, x, v; for (R int i = 1; i <= M; ++ i) { pre = read(), opt = read(), x = read(); if (opt == 1) {v = read(); update(pt[i].id, 1, N, pt[pre].id, x, v);} else {printf("%d\n", query(pt[pre].id, 1, N, x)); pt[i].id = pt[pre].id;} } return 0; }
标签:大小 实现 text print bsp 结果 3.0 位置 space
原文地址:https://www.cnblogs.com/hkttg/p/9419578.html