可持久化线段树

2554. [福利]可持久化线段树

★★☆   输入文件:longterm_segtree.in   输出文件:longterm_segtree.out   简单对比
时间限制:3 s   内存限制:256 MB

【题目描述】

为什么说本题是福利呢?因为这是一道非常直白的可持久化线段树的练习题,目的并不是虐人,而是指导你入门可持久化数据结构。

线段树有个非常经典的应用是处理RMQ问题,即区间最大/最小值询问问题。现在我们把这个问题可持久化一下:

Q k l r 查询数列在第k个版本时,区间[l, r]上的最大值

M k p v 把数列在第k个版本时的第p个数修改为v,并产生一个新的数列版本

最开始会给你一个数列,作为第1个版本。

每次M操作会导致产生一个新的版本。修改操作可能会很多呢,如果每次都记录一个新的数列,空间和时间上都是令人无法承受的。所以我们需要可持久化数据结构:

对于最开始的版本1,我们直接建立一颗线段树,维护区间最大值。

修改操作呢?我们发现,修改只会涉及从线段树树根到目标点上一条树链上logn个节点而已,其余的节点并不会受到影响。所以对于每次修改操作,我们可以只重建修改涉及的节点即可。就像这样:

需要查询第k个版本的最大值,那就从第k个版本的树根开始,像查询普通的线段树一样查询即可。

要计算好所需空间哦

【输入格式】

第一行两个整数N, Q。N是数列的长度,Q表示询问数

第二行N个整数,是这个数列

之后Q行,每行以0或者1开头,0表示查询操作Q,1表示修改操作M,格式为

0 k l r 查询数列在第k个版本时,区间[l, r]上的最大值 或者

1 k p v 把数列在第k个版本时的第p个数修改为v,并产生一个新的数列版本

【输出格式】

对于每个M询问,输出正确答案

【样例输入】

4 5

1 2 3 4

0 1 1 4

1 1 3 5

0 2 1 3

0 2 4 4

0 1 2 4

【样例输出】

4

5

4

4

【提示】

样例解释

序列版本1: 1 2 3 4

查询版本1的[1, 4]最大值为4

修改产生版本2: 1 2 5 4

查询版本2的[1, 3]最大值为5

查询版本1的[4, 4]最大值为4

查询版本1的[2, 4]最大值为4

数据范围

N <= 10000 Q <= 100000

对于每次询问操作的版本号k保证合法,

区间[l, r]一定满足1 <= l <= r <= N


 

【可持久化线段树???主席树???】

有人说可持久化线段树和主席树是一样的,又有人说,这两个不一样,不过无所谓了,反正就是实现空间的优化,同时可以查询以前的版本,大概我就知道这两个功能。每次修改的时间复杂度和增加的空间都是logn的,所以就很优越了。

那么这个优越的数据结构是如何实现的,其实我第一次看到这个高大上的算法名字时,内心是畏惧的,但后来慢慢一看一些大佬的博客,发现还是不是很难,也就是在线段树的基础上增加了一点小技巧,所以新学者不要担心,我这么蠢的都学会了,大家就不虚了。

首先我们还是来看看这张经典的可持久化线段树的图解

 

比如我们现在要修改第3个点的值,我们需要修改什么点呢,就是第3个点的所有祖先节点(图中红色节点),因为那些节点管理的区间包括第3个点(我们就不用重新建树了),当然我们更不能把这些点直接改了,因为以后可能还要查询这个版本。

所以我们就对于每个要被修改的节点,新建一个copy节点,来代替新的版本的这个节点,当然如果没有修改的节点依旧保留原来的连接方式。

然后我们就发现,如果我们要查询第k个版本的区间信息,那我们就从第k个根节点开始查询就可以了,发现是不是很简单啊,就是复制节点+从某版本根节点开始查询。

【代码实现】

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 const int maxn=1e5+5;
 5 struct sd{
 6     int l,r,maxx,son[2];
 7 }t[20*maxn+5];
 8 int root[maxn],tot,cnt;
 9 void update(int v)
10 {
11     int ls=t[v].son[0],rs=t[v].son[1];
12     t[v].maxx=max(t[ls].maxx,t[rs].maxx);
13 }
14 void build(int &v,int l,int r)
15 {
16     cnt++,v=cnt,t[v].l=l,t[v].r=r;
17     if(l==r) {scanf("%d",&t[v].maxx);return;}
18     int mid=(l+r)/2;
19     build(t[v].son[0],l,mid);
20     build(t[v].son[1],mid+1,r);
21     update(v);
22 }
23 int num;
24 void change(int v,int pos,int u)
25 { 
26     if(t[v].l==t[v].r&&t[v].l==pos)
27     {t[v].maxx=u;return;}
28     int mid=(t[v].l+t[v].r)/2,ls=t[v].son[0],rs=t[v].son[1];
29     if(pos<=mid)
30     {
31         cnt++,t[cnt]=t[ls],t[v].son[0]=cnt;
32         change(cnt,pos,u);
33     }
34     else
35     {
36         cnt++,t[cnt]=t[rs],t[v].son[1]=cnt;
37         change(cnt,pos,u);
38     }
39     update(v);
40 }
41 int ask(int v,int l,int r)
42 {
43     if(t[v].l==l&&t[v].r==r)
44     return t[v].maxx;
45     int mid=(t[v].l+t[v].r)/2,ls=t[v].son[0],rs=t[v].son[1];
46     if(r<=mid) return ask(ls,l,r);
47     else if(l>mid) return ask(rs,l,r);
48     else return max(ask(ls,l,mid),ask(rs,mid+1,r));
49 }
50 int main()
51 {
52     int n,m;
53     scanf("%d%d",&n,&m);
54     build(root[++tot],1,n);
55     for(int i=1;i<=m;i++)
56     {
57         int ord;
58         scanf("%d",&ord);//printf("[%d]",ord);
59         if(ord==0)
60         {
61             int k,l,r;
62             scanf("%d%d%d",&k,&l,&r);
63             printf("%d\n",ask(root[k],l,r));
64         }
65         else
66         {
67             int k,pos,u;
68             scanf("%d%d%d",&k,&pos,&u);
69             root[++tot]=++cnt;t[cnt]=t[root[k]];
70             change(root[tot],pos,u);
71         }
72     }
73     return 0;
74 }

 

posted @ 2018-05-03 21:16  genius777  阅读(502)  评论(0编辑  收藏  举报