码迷,mamicode.com
首页 > 其他好文 > 详细

P3834 【模板】可持久化线段树 1(主席树)

时间:2018-08-04 20:24:21      阅读:127      评论:0      收藏:0      [点我收藏+]

标签:大小   实现   text   print   bsp   结果   3.0   位置   space   

P3834 【模板】可持久化线段树 1(主席树)

题目背景

这是个非常经典的主席树入门题——静态区间第K小

数据已经过加强,请使用主席树。同时请注意常数优化

题目描述

如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。

输入输出格式

输入格式:

第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。

第二行包含N个正整数,表示这个序列各项的数字。

接下来M行每行包含三个整数 l,r,k l, r, kl,r,k , 表示查询区间 [l,r][l, r][l,r] 内的第k小值。

输出格式:

输出包含k行,每行1个正整数,依次表示每一次查询的结果

输入输出样例

输入样例#1: 
5 5
25957 6405 15770 26287 26465 
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
输出样例#1: 
6405
15770
26287
25957
26287

说明

数据范围

对于20%的数据满足: 1≤N,M≤101 \leq N, M \leq 101N,M10

对于50%的数据满足: 1≤N,M≤1031 \leq N, M \leq 10^31N,M103

对于80%的数据满足: 1≤N,M≤1051 \leq N, M \leq 10^51N,M105

对于100%的数据满足: 1≤N,M≤2⋅1051 \leq N, M \leq 2\cdot 10^51N,M2105

对于数列中的所有数 aia_iai? ,均满足 −109≤ai≤109-{10}^9 \leq a_i \leq {10}^9109ai?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

dalao的讲解秒懂(灵魂画手)

code

#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;
}

做完才发现还有一道题

 P3919 【模板】可持久化数组(可持久化线段树/平衡树)

题目背景

UPDATE : 最后一个点时间空间已经放大

标题即题意

有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集)

题目描述

如题,你需要维护这样的一个长度为 N N N 的数组,支持如下几种操作

  1. 在某个历史版本上修改某一个位置上的值

  2. 访问某个历史版本上的某一位置的值

此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)

输入输出格式

输入格式:

输入的第一行包含两个正整数 N,M N, M N,M , 分别表示数组的长度和操作的个数。

第二行包含 N N N 个整数,依次为初始状态下数组各位的值(依次为 ai a_i ai?1≤i≤N 1 \leq i \leq N 1iN )。

接下来 M M M 行每行包含3或4个整数,代表两种操作之一( i i i 为基于的历史版本号):

  1. 对于操作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. 对于操作2,格式为 vi 2 loci v_i \ 2 \ {loc}_i vi? 2 loci? ,即访问版本 vi v_i vi? 中的 aloci a_{{loc}_i} aloci?? 的值

输出格式:

输出包含若干行,依次为每个操作2的结果。

输入输出样例

输入样例#1: 
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
输出样例#1: 
59
87
41
87
88
46

说明

数据规模:

对于30%的数据: 1≤N,M≤103 1 \leq N, M \leq {10}^3 1N,M103

对于50%的数据: 1≤N,M≤104 1 \leq N, M \leq {10}^4 1N,M104

对于70%的数据: 1≤N,M≤105 1 \leq N, M \leq {10}^5 1N,M105

对于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}^91N,M106,1loci?N,0vi?<i,109ai?,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一发

code

#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;
}

 

P3834 【模板】可持久化线段树 1(主席树)

标签:大小   实现   text   print   bsp   结果   3.0   位置   space   

原文地址:https://www.cnblogs.com/hkttg/p/9419578.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!