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

主席树的学习

时间:2018-11-05 22:24:13      阅读:259      评论:0      收藏:0      [点我收藏+]

标签:width   离散化   改变   复杂度   可持久化线段树   unique   print   代码   .com   

前言

主席树可真是个好东西

之前一直都觉得挺难的

今天一看

woc这么简单!

怎么可能,我还是太蒟蒻了

感谢akakw1大佬的指导!


 

正文:

一.前置知识及算法思路

1.可持久化

因为主席树是可持久化线段树,所以还是有必要了解一下可持久化

可持久化的数据结构是可以支持访问任一历史版本的(也就是每一次修改操作之前的情况)

 

2.如何实现

以可持久化线段树为例:

很自然的,我们可以想到对于每一个版本开一个线段树

但考虑到这样做的空间复杂度是\(O(k*n*4)\) (k为修改次数)

果断放弃

3.优化

考虑进行优化.

盗一张图

 

技术分享图片

假如我们现在要修改的点是4(也就是区间[4,4])

可以发现区间[1,3]和区间[5,6]是完全没有改变的,

因此我们考虑利用上一版本

具体解释:当我们递归[1,6]的左子树[1,3]时发现4根本没有在其中,这也就意味着当前版本的[1,3]节点与上一版本完全一样!

因此我们直接让新开的根节点(也就是橙色的[1.6])的左子节点指向原来的根节点(也就是蓝色的[1.6])的左子节点(蓝色的[1,3]);

接着继续递归右子树[4,6],发现[6,6]也可以直接利用上一版本,以此类推

然后就发现每一次就只要开\(log_2 n\)个节点了!

二.例题

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

题目就是让你查询区间[l,r]的第k小值

离散化一下值域

这题开个可持久化值域线段树就可以啦

代码

 1 #include<bits/stdc++.h>
 2 #define R register int
 3 #define gc getchar
 4 using namespace std;
 5 const int MAX_N=2e5+10,MAX_MLOGN=4e6+10;
 6 int tot,root[MAX_N],n,m,q,a[MAX_N],b[MAX_N];
 7 int rd()
 8 {
 9     int ans=0,flag=1;
10     char ch=gc();
11     while((ch<0||ch>9)&&ch!=-)ch=gc();
12     if(ch==-)flag=-1,ch=gc();
13     while(ch>=0&&ch<=9)ans=ans*10+ch-48,ch=gc();
14     return ans*flag;
15 }
16 struct Segment_Tree{
17     int l,r;
18     int dat;
19 }t[MAX_MLOGN];
20 
21 //l,r为左右子节点编号
22 //dat是当前版本的序列下 x的个数(L_i<=x<=R_i(i为节点编号,这里的L,R*要与结构体中的l,r加以区分))
23 //*这里的L,R是指当前节点在线段树中所代表的值域[L,R]
24 
25 int Build(int l,int r)//l,r都为值域,函数的返回值是节点编号
26 //调用入口为Build(1,m) *(1,m)为离散后的值域
27 {
28     int p=++tot;
29     t[p].dat=0;
30     //因为主席树不再是完全二叉树,不满足t[p*2]是t[p]的子节点
31     //所以直接用tot来记录节点编号
32     if(l>=r)
33     {
34         return p;
35     }
36     int mid=(l+r)>>1;
37     t[p].l=Build(l,mid);
38     t[p].r=Build(mid+1,r);
39     return p;
40 }
41 int Insert(int pre,int l,int r,int x)//pre是上一版本的同一位置的节点的编号;l,r为值域;x为值
42 //函数返回值仍然是节点编号
43 {
44     int p=++tot;
45     t[p].l=t[pre].l,t[p].r=t[pre].r;
46     t[p].dat=t[pre].dat+1;//可以先直接继承上一版本的数据
47     int mid=(l+r)>>1;
48     if(l<r)
49     {
50         if(x<=mid)//按值域划分,不用多说
51         {
52             t[p].l=Insert(t[pre].l,l,mid,x);//这里是递归t[pre]而不是t[p]
53         }
54         else
55         {
56             t[p].r=Insert(t[pre].r,mid+1,r,x);//同理
57         }
58     }
59     return p;
60 }
61 int Query(int u,int v,int l,int r,int k)
62 {
63     if(l>=r)return l;
64     int mid=(l+r)>>1;
65     int tmp=t[t[v].l].dat-t[t[u].l].dat;
66     if(tmp<k)return Query(t[u].r,t[v].r,mid+1,r,k-tmp);
67     else return Query(t[u].l,t[v].l,l,mid,k);
68 }
69 int main()
70 {
71     n=rd(),q=rd();
72     for(R i=1;i<=n;i++)
73     {
74         b[i]=a[i]=rd();
75     }
76     /**/
77     sort(b+1, b+1+n);
78     m=unique(b+1,b+1+n)-b-1;//离散化
79     /**/
80     root[0]=Build(1,m);
81     for(R i=1;i<=n;i++)
82     {
83         R t=lower_bound(b+1,b+1+m,a[i])-b;
84         root[i]=Insert(root[i-1],1,m,t);
85     }
86     for(R i=1;i<=q;i++)
87     {
88         int x=rd(),y=rd(),k=rd();
89         R tmp=Query(root[x-1],root[y],1,m,k);
90         printf("%d\n",b[tmp]);
91     }
92     return 0;
93 }

我太蒟了所以加了一大波注释(怕自己以后看不懂)

我太蒟了所以以上文字有什么问题可以在评论区回复

话说会有人看吗QAQ

 

主席树的学习

标签:width   离散化   改变   复杂度   可持久化线段树   unique   print   代码   .com   

原文地址:https://www.cnblogs.com/Zenyz/p/9873574.html

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