主席树 【权值线段树】 && 例题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$大的元素是哪一个 题解: 写法很多,主席树是最常用的一种之一 除此之外有:划分树,莫队分块,平衡树等 主席树的定义其实挺模糊, 一般认为就是可持久 ...
随机推荐
- Java Mybatis快速入门之基本使用
目录 搭建环境 编写 Mybatis 核心配置文件 pom导出资源失败 测试 搭建环境 新建Maven项目 导入Maven依赖 <dependencies> <!--mysql驱动- ...
- Java并发/多线程-CAS原理分析
目录 什么是CAS 并发安全问题 举一个典型的例子i++ 如何解决? 底层原理 CAS需要注意的问题 使用限制 ABA 问题 概念 解决方案 高竞争下的开销问题 什么是CAS CAS 即 compar ...
- 【RAC】运行root.sh的时候报错root.sh Oracle CRS stack is already configured and will be running under init(1M)
环境:oracle10g 系统:CentOS6.4 开始的时候,在节点1上运行root.sh发现出现90s 的时候hang住了,结束掉,结局完事后,再次运行root.sh报错 WARNING: dir ...
- leetcode 730. 统计不同回文子序列(区间dp,字符串)
题目链接 https://leetcode-cn.com/problems/count-different-palindromic-subsequences/ 题意 给定一个字符串,判断这个字符串中所 ...
- 攻防世界—pwn—level0
题目分析 下载文件后首先使用checksec检查文件保护机制 文件名太长了,就更改了一下 发现是一个64位程序,使用ida查看伪代码 注意到一个特殊的函数名callsystem 确定思路,直接栈溢出 ...
- (hive)hive优化(转载)
1. 概述 1.1 hive的特征: 可以通过SQL轻松访问数据的工具,从而实现数据仓库任务,如提取/转换/加载(ETL),报告和数据分析: 它可以使已经存储的数据结构化: 可以直接访问存储在Apac ...
- v-show和v-if指令的共同点和不同点?
共同点:都能控制元素的显示和隐藏:不同点:实现本质方法不同,v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次:v-if是动态的向DOM树内添加或者删除DOM元 ...
- Java并发包源码学习系列:阻塞队列实现之DelayQueue源码解析
目录 DelayQueue概述 类图及重要字段 Delayed接口 Delayed元素案例 构造器 put take first = null 有什么用 总结 参考阅读 系列传送门: Java并发包源 ...
- LOJ10082
题目描述 原题来自:Centrual Europe 2005 我们有N个字符串,每个字符串都是由 a 至 z 的小写英文字母组成的.如果字符串A的结尾两个字符刚好与字符串B的开头两个字符匹配,那么我们 ...
- 有趣的css—隐藏元素的7种思路
css隐藏元素的7种思路 前言 display.visibility.opacity三个属性隐藏元素之间的异同点一直是前端面试面试的常考题. 属性 值 是否在页面上显示 注册点击事件是否有效 是否存在 ...