第一行——咕咕咕。
第二行——最近是为了祖国的绿色持续种树的职业鸽手。
以下为正文:
首先明确:可持久化数据结构就是保留整个操作历史。体现在线段树上,就是在进行修改时,在保留原有的数据的基础上,把更新后的加入(就是新建一部分树)(为什么是新建一部分呢,为了省空间),将更新的点与其相关父节点新建即可。
与普通线段树不同的是:主席树的节点维护的是输入数组在每个节点上的区间内出现的次数(反正按我的理解是这样好的我知道非常拗口)。普通线段树的节点是区间和。
查询:在最初建一个空线段树,每次输入数组值,即新建相关的点进行更新,同时保留原有的节点值,形成一个有n+1个根节点的线段树(n为数组长度)(好的我知道没有图很难理解)(但我不会画图啊委屈.jpg)。它存的是每个前缀的元素出现次数,我们在求区间时,只需作差即可。(我后续会尽量补图的)(咕咕咕)。
其他:1.需要先对输入数组进行离散化。2.主要用于区间求k大值。3.空间复杂度为n*logn,因为不停新建树4.需要先学习离散化和线段树
注:离散化数组可用stl中的unique函数。unique是一个“去重”函数,将重复的元素放到末尾,然后返回一个迭代器的值(但是并没有删除元素,只是将重复元素置于数组末尾)。返回的是最后一个不同元素的下标,减去首元素的地址即可得到离散后数组的大小。
我个人觉得过程很好理解,但怎么放在代码里就这么难受呢?
本代码以洛谷P3834为例(其实这份代码是跟洛谷上学的)
具体解释在代码里
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define ll long long
#define maxn 200005
using namespace std;
int tree[maxn*32] = {0};//每个点的值
int le[maxn*32] = {0};//左右子树
int ri[maxn*32] = {0};
int a[maxn];
int b[maxn];
int cx[maxn];//某种意义上存的是每棵树的根节点,从该点开始遍历可得到一棵新树
int n,cs;
int cnt = 0;
int build(int x,int y)
{
cnt++;//从1开始存节点
int rt = cnt;//当前节点编号
if(x != y)
{
int m = (x+y) >> 1;
le[rt] = build(x,m);
ri[rt] = build(m+1,y);
}
return rt;//返回当前节点的编号
}
int add(int pr,int l,int r,int x)
{
cnt++;
int rt = cnt;
le[rt] = le[pr];
ri[rt] = ri[pr];
tree[rt] = tree[pr]+1;//插入的离散后的a[i]在该点代表的区间,所以++
if(l != r)
{
int m = (l+r) >> 1;
if(x <= m) le[rt] = add(le[pr],l,m,x);
else ri[rt] = add(ri[pr],m+1,r,x);
}//判断更新的是左or右子树
return rt;
}
int query(int l,int r,int x,int y,int k)
{
if(x == y) return b[x];
int m = (x+y)>>1;
int rt = tree[le[r]] - tree[le[l]];
//传过来的是cx[l-1]和cx[r]
//tree存的是1~l-1和1~r的出现次数
//作差即为区间l~r
if(rt >= k) return query(le[l],le[r],x,m,k);//左子树
else return query(ri[l],ri[r],m+1,y,k-rt);//右子树
//在右子树(本来就比左子树大了)找第k-rt小的值
}//l,r区间的第k小,x,y为当前区间
int main()
{
scanf("%d%d",&n,&cs);
for(int i = 1; i <= n; ++i)
scanf("%d",&a[i]),b[i] = a[i];
sort(b+1,b+n+1);
int siz = unique(b+1,b+n+1)-b-1;//对数据进行去重
cx[0] = build(1,siz);//建立一棵空树
for(int i = 1; i <= n; ++i)
{
int q = lower_bound(b+1,b+siz+1,a[i])-b;//实际上是按a[i]的大小进行标号
//-b可以做到标号从1开始
cx[i] = add(cx[i-1],1,siz,q);//建新树,并把根节点赋给cx数组
}
while(cs--)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
int ans = query(cx[l-1],cx[r],1,siz,k);//查询的是第cx[l]开始的树所以是传cx[l-1]
//因为函数中会删去
printf("%d\n",ans);
}
return 0;
}
写这份代码的心路历程:
//我个人觉得结构体版的思路更清晰一些,所以先用这个写一下
//线段树我是用数组写的,我会再写一个数组版本的
//我不会说是因为数组版的代码量小一些
//我收回我的话结构体版一点都不清晰
//代码快要给爸爸看吐了
//很好我回归数组版了
应该会补其他练习题的!
欢迎指出错误qwq