主席树 【权值线段树】 && 例题K-th Number POJ - 2104
一、主席树与权值线段树区别
主席树是由许多权值线段树构成,单独的权值线段树只能解决寻找整个区间第k大/小值问题(什么叫整个区间,比如你对区间[1,8]建立一颗对应权值线段树,那么你不能询问区间[2,5]第k大/小值,你只能询问[1,8]第k大/小值问题)
二、权值线段树是什么鬼
学权值线段树之前你肯定要知道线段树,线段树是对区间中的所有数进行维护
权值线段树维护的对象和它不同,权值<==>这个数在这个区间中出现的次数
例如1、5、3、2、4
建立的权值线段树:
你会发现,权值线段树的建立和线段树不一样,线段树要考虑每一个数在区间中的位置
但是权值线段树不用管,相当于排完序之后建立的一颗树 。 要注意:权值代表这个数出现了几次
例如:我们寻找这个区间的第3小值
我们从根节点[1,5]的左节点来判断,因为左节点的权值要大于3,所以区间内第3小值肯定在根节点的左区间内
我们再看区间[1,3]的左节点,发现它的左节点权值为2小于3,所以答案肯定在区间[1,3]的右节点内。注意:这个时候我们要在右节点内寻找第3-2小值
之后那区间[3,3]内第1小值肯定就是3本身了
要注意如果题目给你的数据特别大,例如1、1000、200000000这三个数,我们要是直接建立权值线段树,那内存就炸了
所以这个时候就要离散化处理
三、主席树构成
我们文章开头说过,主席树是由许多权值线段树构成,而且主席树可以解决:在对应区间建立的主席树,可以找寻该区间所有子区间内的第k大/小值
(即、对区间[1,5]建立主席树,那么我们也可以找寻区间[1,3]的第k大/小值)
1、解决方法1:
排序后找到那个位置输出(不用想了,暴力方法肯定会被卡)
2、解决方法2:
对于一个区间[l, r]我们用一个用这个区间内出现过的数的个数组成一颗权值线段树,然后查询就完事了
但是多次询问区间第k小,我们每次这样建立一个线段树,这样不仅空间复杂度非常之高,而且时间复杂度也非常高,甚至比普通排序还要高,那么我们只不是可以想一个办法,使得对于每次我们查询不同的区间我们不需要重新建树,如果这样。时间复杂度和空间复杂度就大大降低了。
3、解决方法3:
这就用到了前缀和,比如我们有一个问题。就是每次静态的求区间和,我们可以预处理所以的前缀和sum[i],我们每次求解区间[l, r]和时,我们可以直接得到答案为sum[r] - sum[l -1],这样就不需要对区间中的每个数进行相加来求解了。(这里我们设sum[i]中放的是权值线段树上i节点的权值)
想到就写起来
我们可以对区间[1,5]建立6棵权值线段树,分别是[0,0],[1,1],[1,2],[1,3],[1,4],[1,5] ([0,0]是一颗空树)
这样我们处理任意区间[l, r]时就可以通过处理区间[1,l - 1], [1,r],就行,然后两者的处理结果进行相加相减就行。为什么满足相加减的性质,我们简单分析一下就很容易得出。如果在区间[1,l - 1]中有x个数小于一个数,在[1,r]中有y个数小于那个数,那么在区间[l,r]中就有y - x 个数小于那个数了,这样就很好理解为什么可以相加减了,另外,每颗树的结构都一样,都是一颗叶节点为n个的线段树。
上述利用前缀和的思想只是解决了时间复杂度的问题,并没有解决空间复杂度的问题,要解决空间复杂度问题。我们需要用到线段树的性质,我们每次更新一个数,那么与更新之前相比,这颗线段树改变只是一条链(从根节点到某一叶节点),那么我们可以充分利用这个特点,因为第i颗树与第i- 1颗树相比,只是更新了第i个元素,那么实际上第i颗树与第i-1颗树之间只有log个节点的信息是不同的。.所以这两棵树有很多相同的节点,所以这两棵树可以共用很多节点,也就是说,我们在第i-1颗树上 插入一个节点a[i] 得到第i颗权值线段树,而单点插入过程中只会修改根到那个叶子节点的路径上的那log个节点。于是这样就解决空间复杂度问题。
还是以上面的1、3、2、5、4为例子:
1、刚开始的空树
2、加一个1节点
3、加一个2节点
4、加一个节点3
后面的4和5号节点就不写了,大家懂了就行。。。
四、主席树复杂度
插入一个点的时空复杂度都为O(log n),所以建立这颗主席树【权值线段树总体】的时空复杂度就是O(n log n),单次询问经过log n个节点,时间复杂度也为O(log n)
5、例题
K-th Number POJ - 2104
代码1:
1 #include<stdio.h>
2 #include<iostream>
3 #include<algorithm>
4 #include<string.h>
5 using namespace std;
6 const int maxn=1e5+10;
7 int cnt,ranks[maxn],v[maxn],root[maxn];
8 struct shudui
9 {
10 int value,id;
11 }w[maxn];
12 struct Node
13 {
14 int l,r,sum;
15 Node(){
16 sum=0;
17 }
18 }tree[maxn*20];
19 bool mmp(shudui x,shudui y)
20 {
21 return x.value<y.value;
22 }
23 void init()
24 {
25 cnt=1;
26 root[0]=0;
27 tree[0].l=tree[0].r=tree[0].sum=0;
28 }
29 void inserts(int num,int &rt,int l,int r)
30 {
31 tree[cnt++]=tree[rt];
32 rt=cnt-1;
33 tree[rt].sum++;
34 if(l==r) return;
35 int mid=(l+r)>>1;
36 if(num<=mid) inserts(num,tree[rt].l,l,mid);
37 else inserts(num,tree[rt].r,mid+1,r);
38 }
39 int query(int i,int j,int k,int l,int r)
40 {
41 int d=tree[tree[j].l].sum-tree[tree[i].l].sum;
42 if(l==r) return l;
43 int mid=(l+r)>>1;
44 if(k <= d) return query(tree[i].l, tree[j].l, k, l, mid); //这里是小写的L,mid可不是数字1
45 else return query(tree[i].r, tree[j].r, k - d, mid + 1, r);
46 }
47 int main()
48 {
49 int n,m;
50 scanf("%d%d",&n,&m);
51 for(int i=1;i<=n;++i)
52 {
53 scanf("%d",&w[i].value);
54 w[i].id=i;
55 }
56 sort(w+1,w+1+n,mmp);
57 w[0].value=-1;
58 int j=1;
59 for(int i=1;i<=n;++i)
60 {
61 if(w[i].value!=w[i-1].value)
62 ranks[w[i].id]=j,v[j]=w[i].value,j++;
63 else ranks[w[i].id]=j-1;
64 }
65 init();
66 for(int i=1;i<=n;++i)
67 {
68 //printf("%d %d\n",ranks[i],v[i]);
69 root[i]=root[i-1];
70 inserts(ranks[i],root[i],1,n);
71 }
72 while(m--)
73 {
74 int l,r,x;
75 scanf("%d%d%d",&l,&r,&x);
76 printf("%d\n",v[query(root[l-1],root[r],x,1,n)]);
77 }
78 return 0;
79 }
代码2:
1 //洛谷 P3834 可持久化线段树(主席树)
2
3 #include<cstdio>
4
5 #include<cstring>
6
7 #include<algorithm>
8
9 using namespace std;
10
11 const int N=200005;
12
13 int n,m,q,t=0;
14
15 int a[N],b[N],root[N];
16
17 struct node
18
19 {
20
21 int ls,rs,sum;
22
23 }tree[N*20];
24
25 void disc()
26
27 {
28
29 int i;
30
31 sort(b+1,b+n+1);
32
33 m=unique(b+1,b+n+1)-(b+1);
34
35 for(i=1;i<=n;++i)
36
37 a[i]=lower_bound(b+1,b+m+1,a[i])-b;
38
39 }
40
41
42
43 //insert函数就是说:当前插入的数p,会影响节点x,所以把x节点的sum加1.
44
45
46
47 //节点x代表一个权值区间,影响x就是说p在节点x所代表的权值区间内。
48
49 //那么先把前一个树的对应区间的节点复制过来,再加1,就行了。
50
51 //可以结合刚才的图感性理解一下。
52
53 void insert(int y,int &x,int l,int r,int p)
54
55 {
56
57 x=++t; //t相当于是一个节点的地址,每个节点是不同的。
58
59 tree[x]=tree[y]; //复制前一个树的对应节点【它们代表的权值区间相同】。
60
61 tree[x].sum++; //给这个节点的sum加1.(这个1指的就是p)
62
63 if(l==r) return; //搜索到根节点就返回。
64
65 int mid=(l+r)>>1;
66
67
68
69 //判断在哪个区间继续插入。
70
71 if(p<=mid) insert(tree[y].ls,tree[x].ls,l,mid,p);
72
73 else insert(tree[y].rs,tree[x].rs,mid+1,r,p);
74
75 }
76
77
78
79 //k是查询第k小
80
81 //x和y相当于是树的节点的地址。而l和r就是这两个节点的权值区间。
82
83 //一开始query(root[l-1],root[r],1,m,k)。
84
85 //root[l-1]就是第l-1颗树的根节点。root[r]就是第r颗树的根节点。
86
87 //比较它们左儿子代表的区间中的数的个数,差值为delta。根据delta判断这两个节点一起往哪个方向跳。
88
89 //分析过程和刚才二分的过程一样。
90
91 int query(int x,int y,int l,int r,int k)
92
93 {
94
95 if(l==r) return l; //查到权值线段树的叶子节点就返回这个值。
96
97 int delta=tree[tree[y].ls].sum-tree[tree[x].ls].sum;
98
99 int mid=(l+r)>>1;
100
101 if(k<=delta) return query(tree[x].ls,tree[y].ls,l,mid,k);
102
103 else return query(tree[x].rs,tree[y].rs,mid+1,r,k-delta);
104
105 }
106
107 int main()
108
109 {
110
111 int l,r,i,k;
112
113 scanf("%d%d",&n,&q);
114
115 for(i=1;i<=n;++i)
116
117 {
118
119 scanf("%d",&a[i]);
120
121 b[i]=a[i];
122
123 }
124
125 disc();
126
127 for(i=1;i<=n;++i)
128
129 insert(root[i-1],root[i],1,m,a[i]);
130
131 for(i=1;i<=q;++i)
132
133 {
134
135 scanf("%d%d%d",&l,&r,&k);
136
137
138
139 //query函数返回的是第k小的权值。
140
141 //把这个权值转化为原来这个权值对应的数就行了。
142
143 printf("%d\n",b[query(root[l-1],root[r],1,m,k)]);
144
145 }
146
147 return 0;
148
149 }
参考博客:
https://blog.csdn.net/g21glf/article/details/82986968
https://blog.csdn.net/creatorx/article/details/75446472
主席树 【权值线段树】 && 例题K-th Number POJ - 2104的更多相关文章
- 线段树(单标记+离散化+扫描线+双标记)+zkw线段树+权值线段树+主席树及一些例题
“队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄> 线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...
- 【BZOJ3065】带插入区间K小值 替罪羊树+权值线段树
[BZOJ3065]带插入区间K小值 Description 从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理 ...
- [bzoj3196][Tyvj1730]二逼平衡树_树套树_位置线段树套非旋转Treap/树状数组套主席树/权值线段树套位置线段树
二逼平衡树 bzoj-3196 Tyvj-1730 题目大意:请写出一个维护序列的数据结构支持:查询给定权值排名:查询区间k小值:单点修改:查询区间内定值前驱:查询区间内定值后继. 注释:$1\le ...
- [bzoj3524==bzoj2223][Poi2014]Couriers/[Coci 2009]PATULJCI——主席树+权值线段树
题目大意 给定一个大小为n,每个数的大小均在[1,c]之间的数列,你需要回答m个询问,其中第i个询问形如\((l_i, r_i)\),你需要回答是否存在一个数使得它在区间\([l_i,r_i]\)中出 ...
- BZOJ 4777 Usaco2017 Open Switch Grass Kruskal+替罪羊树+权值线段树
这道题首先可以看出答案一定是一条边,而且答案一定在最小生成树上,那么我们就可以在这个最小生成树上维护他与异色儿子的边最小值,所以我们就可以已通过Kruskal和一棵平衡树来解决,时间复杂度是O(n*l ...
- 洛谷P4848 崂山白花蛇草水 权值线段树+KDtree
题目描述 神犇 \(Aleph\) 在 \(SDOI\ Round2\) 前立了一个 \(flag\):如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇 \(Aleph\) 的实力,他轻松地进了山 ...
- HDU6621 K-th Closest Distance 第 k 小绝对值(主席树(统计范围的数有多少个)+ 二分 || 权值线段树+二分)
题意:给一个数组,每次给 l ,r, p, k,问区间 [l, r] 的数与 p 作差的绝对值的第 k 小,这个绝对值是多少 分析:首先我们先分析单次查询怎么做: 题目给出的数据与多次查询已经在提示着 ...
- 动态求区间K大值(权值线段树)
我们知道我们可以通过主席树来维护静态区间第K大值.我们又知道主席树满足可加性,所以我们可以用树状数组来维护主席树,树状数组的每一个节点都可以开一颗主席树,然后一起做. 我们注意到树状数组的每一棵树都和 ...
- HDU - 2665 Kth number 主席树/可持久化权值线段树
题意 给一个数列,一些询问,问$[l,r]$中第$K$大的元素是哪一个 题解: 写法很多,主席树是最常用的一种之一 除此之外有:划分树,莫队分块,平衡树等 主席树的定义其实挺模糊, 一般认为就是可持久 ...
随机推荐
- 与HBase对比,Cassandra的优势特性是什么?
在1月9日Cassandra中文社区开年活动开始之前的闲聊时间,活动的四位嘉宾就"HBase和Cassandra的对比"这一话题展开了讨论. 总的来说,HBase和Cassan ...
- puppetlabs地址
https://yum.puppetlabs.com/el/6Server/products/i386/ rpm -Uvh http://yum.puppetlabs.com/el/6Server/ ...
- 奇技淫巧,还是正统功夫? - Python推导式最全用法
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取 python免费学习资 ...
- 使用sqluldr2进行oracle数据库抽取时执行后无反应,也无日志
使用sqluldr2进行oracle数据库表数据抽取时遇到执行后无反应,也不报错,也无日志输出的情况. 经过排查之后发现时由于oracle账户密码快要过期导致的(这也能出问题,我服,类似的plsql连 ...
- 未使用绑定变量对share_pool的影响
oracle SGA中包含数据高速缓冲,重做日志缓冲,以及共享池(share_pool).共享池中包含库高速缓冲(所有的SQL,执行计划等)和数据字典缓冲(对象的定义,权限等). 所以,如果SQL中没 ...
- 目标检测的评价指标(TP、TN、FP、FN、Precision、Recall、IoU、mIoU、AP、mAP)
1. TP TN FP FN GroundTruth 预测结果 TP(True Positives): 真的正样本 = [正样本 被正确分为 正样本] TN(True Negatives): 真的 ...
- Django-发上云服务器遇到的问题
1.服务器启动后外网访问显示A server error occurred. Please contact the administrator. 解决方法:原文:https://www.cnblogs ...
- 聊聊.net应用程序的Docker镜像
要在容器中运行.net应用程序,你需要在容器镜像中安装.net Framework或.net Core 运行时.这不是你需要自己管理的东西,因为微软提供的Docker镜像已经安装了运行时,你可以使用 ...
- 前端面试之ES6新增了数组中的的哪些方法?!
前端面试之ES6新增了数组中的的哪些方法?! 我们先来看看数组中以前有哪些常用的方法吧! 1 新增的方法! 1 forEach() 迭代遍历数组 回调函数中的三个参数 value: 数组中的每一个元素 ...
- centos下解压rar文件,Linux解压tar.gz和tar.bz2的命令
1.下载:根据主机系统下载合适的版本,当前64为centos系统演示下载: wget http://www.rarlab.com/rar/rarlinux-x64-5.3.0.tar.gz 2.解压安 ...