建议先了解一下动态开点线段树,可以参考一下我写的这篇
什么是可持久化线段树:
若询问次数为q,维护区间为m
则所需空间为O(q*logm)
通过root[]数组,记录不同版本的根节点编号,我们可以方便的在某个版本上修改和查询
问题1:什么是不同的版本?
我在某个版本的线段树上做了某次修改,修改后的线段树,就是不同与之前的一个船新版本。
问题2:为什么只要O(q*logm)?
对于在某个版本上进行的单点修改,我们只涉及logm个节点需要修改(与动态开点线段树一样的思想)
剩下的沿用老版本的即可,因此,建立一个新版本(进行一次修改操作)只需要新建logm个节点(区间修改不会证明)
看图看图:
讲一下更新和询问的代码:
一、这是需要的数组和偷懒的宏定义
#define tl tree[rt].l
#define tr tree[rt].r
#define mid int m=l+r>>1
#define lson tl,l,m
#define rson tr,m+1,r
struct node
{
int l,r;
int sum;
}tree[M];
二、update
void update(int old,int p,int x,int& rt,int l,int r)
{
rt = sz++;
if (l==r){
tree[rt].val = x;
return ;
}
tree[rt] = tree[old];
mid;
if (p<=m) update(tl,p,x,lson);
else update(tr,p,x,rson);
//这里一般都需要push_up
}
old表示老版本维护这段区间的节点编号,p表示单点修改的位置,x表示要修改的值,二分找区间,p所在的那颗子树一定是需要修改的,因此那个节点就要新开辟
即rt = sz++。
值的注意的是这个int &rt,可以方便的将上一层递归的rt的左/右儿子赋值。
以及新节点首先获得老版本的全部信息,即tree[rt] = tree[old],再去递归修改需要修改的儿子节点
主函数中:
update(root[所需的老版本],p,x,root[当前新版本],1,维护的区间右界)
这样可以在更新完之后获得当前版本的根节点编号
三、query
int query(int p,int rt,int l,int r)
{
if (l==r){
return tree[rt].val;
}
mid;
if (p<=m) return query(p,lson);
else return query(p,rson);
}
查询第x个版本主函数就写:
query(p,root[x],1,维护的区间右界)
例题一:
代码上面给的差不多了,自己补补全,检验一下自己学会了否
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define tl tree[rt].l
#define tr tree[rt].r
#define mid int m = l+r>>1
#define lson tl,l,m
#define rson tr,m+1,r
const int N = 1e6+5;
const int M = 1000000*20+5;
struct node
{
int l,r;
int val;
}tree[M];
int root[N],sz;
int a[N];
void update(int old,int p,int x,int& rt,int l,int r)
{
rt = sz++;
if (l==r){
tree[rt].val = x;
return ;
}
tree[rt] = tree[old];
mid;
if (p<=m) update(tl,p,x,lson);
else update(tr,p,x,rson);
}
int query(int p,int rt,int l,int r)
{
if (l==r){
return tree[rt].val;
}
mid;
if (p<=m) return query(p,lson);
else return query(p,rson);
}
void build(int& rt,int l,int r)
{
rt = sz++;
if (l==r){
tree[rt].val = a[l];
return ;
}
mid;
build(lson);
build(rson);
}
/*
void display(int rt,int l,int r)
{
if (l==r) {printf("%d ",tree[rt].val);return ;}
mid;
display(lson);
display(rson);
}
*/
int main()
{
int n,m;
scanf("%d %d",&n,&m);
sz = 1;
for1(i,1,n) scanf("%d",a+i);
build(root[0],1,n);
int ver,op,pos,x;
for1(i,1,m){
scanf("%d %d %d",&ver,&op,&pos);
if (op==1){
scanf("%d",&x);
update(root[ver],pos,x,root[i],1,n);
//printf("Verson %d:",i);display(root[i],1,n);puts("");
}
else {
root[i] = root[ver];
//printf("Verson %d:",i);display(root[i],1,n);puts("");
printf("%d\n",query(pos,root[ver],1,n));
}
}
return 0;
}
例题二:
这题讲一下,求静态区间第k小
方法一
先把数据离散化
主席树维护什么:区间内这些数出现的次数(比如询问[1,3]表示我们需要知道1,2,3这几个数出现了几次)
第x个版本维护:前x个数扔进树中,区间这些数出现的次数
那么询问区间[l,r]第k大
某段[x,y]区间内的数出现的次数等于版本r[x,y]这些数出现次数-版本l-1[x,y]这些数出现次数
也就是说去掉了前l-1个数的影响
总结:l,r索引版本,与我们处理哪部分区间无关
接下去二分逼近,
举个例子,我寻找[3,6]第2小,维护的区间大小为[1,1000]
我们需要用:版本6-版本2 = 获取只有3~6这几个数的线段树状态
然后判断第2小的数在左儿子还是右儿子
用sum(x,y)表示这段区间的数出现次数
if (sum(1,500)>=2)在左区间中继续找
else 查找右子树([501,1000])中第 2 - sum(1,500)小的数即可
递归重点是,区间被被逼进到l==r
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define tl tree[rt].l
#define tr tree[rt].r
#define mid int m=l+r>>1
#define lson tl,l,m
#define rson tr,m+1,r
const int N = 2e5+5;
const int M = 200000*20+5;
int a[N];
int data[N];
void discrete(int n)//使用该函数把a[i]变成原本a[i]离散化后对应的数,data可以根据离散化后的值推实际的大小
{
for1(i,1,n) data[i] = a[i];
sort(data+1,data+1+n);
int cnt = unique(data+1,data+1+n) - data;
for1(i,1,n) a[i] = lower_bound(data+1,data+cnt,a[i]) - data;
}
struct node
{
int l,r;
int sum;
}tree[M];
int sz,root[N];
void push_up(int rt){
tree[rt].sum = tree[tl].sum + tree[tr].sum;
}
void update(int old,int p,int& rt,int l,int r)
{
rt = sz++;//这里容易脑抽写成if(!rt) rt = sz++
if (l==r){
tree[rt].sum = tree[old].sum + 1;
return ;
}
tree[rt] = tree[old];
mid;
if (p<=m) update(tl,p,lson);
else update(tr,p,rson);
push_up(rt);
}
int ccnt;
int query(int old,int k,int rt,int l,int r)
{
if (l==r) return data[l];
mid;
ccnt = tree[tl].sum - tree[tree[old].l].sum;
if (ccnt >= k) return query(tree[old].l,k,lson);
else return query(tree[old].r,k-ccnt,rson);
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
sz = 1;
for1(i,1,n) scanf("%d",a+i);
discrete(n);
for1(i,1,n) update(root[i-1],a[i],root[i],1,n);
int l,r,k;
for1(i,1,m){
scanf("%d %d %d",&l,&r,&k);
printf("%d\n",query(root[l-1],k,root[r],1,n));
}
return 0;
}
其实也可以不离散化,毕竟都动态开点了,直接逼近即可
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define mid int m = l+r>>1
#define lson tree[rt].l,l,m
#define rson tree[rt].r,m+1,r
#define tl tree[tree[rt].l]
#define tr tree[tree[rt].r]
#define Min (-1e9)
#define Max (1e9)
namespace FastI{//快读板子,不用管
const int SIZE = 1 << 16;
char buf[SIZE], str[64];
int l = SIZE, r = SIZE;
int read(char *s) {
while (r) {
for (; l < r && buf[l] <= ' '; l++);
if (l < r) break;
l = 0, r = int(fread(buf, 1, SIZE, stdin));
}
int cur = 0;
while (r) {
for (; l < r && buf[l] > ' '; l++) s[cur++] = buf[l];
if (l < r) break;
l = 0, r = int(fread(buf, 1, SIZE, stdin));
}
s[cur] = '\0';
return cur;
}
template<typename type>
bool read(type &x, int len = 0, int cur = 0, bool flag = false) {
if (!(len = read(str))) return false;
if (str[cur] == '-') flag = true, cur++;
for (x = 0; cur < len; cur++) x = x * 10 + str[cur] - '0';
if (flag) x = -x;
return true;
}
template <typename type>
type read(int len = 0, int cur = 0, bool flag = false, type x = 0) {
if (!(len = read(str))) return false;
if (str[cur] == '-') flag = true, cur++;
for (x = 0; cur < len; cur++) x = x * 10 + str[cur] - '0';
return flag ? -x : x;
}
} using FastI::read;
const int N = 2e5;
const int M = 32*2e5 + 5;
struct T
{
int l,r;
int cnt;
}tree[M];
int root[M],sz,nowv;
void push_up(int rt){
tree[rt].cnt = tl.cnt + tr.cnt;
}
void update(int p,int old,int&rt,int l,int r){
rt = sz++;
tree[rt] = tree[old];
if (l==r){
tree[rt].cnt++;
return ;
}
mid;
if (p<=m) update(p,tree[old].l,lson);
else update(p,tree[old].r,rson);
push_up(rt);
}
int query(int old,int k,int rt,int l,int r){
if (l==r) return l;
mid;
int d = tl.cnt - tree[tree[old].l].cnt;
if (k<=d) return query(tree[old].l,k,lson);
else return query(tree[old].r,k-d,rson);
}
int main()
{
//freopen("C:/Users/DELL/Desktop/input.txt", "r", stdin);
//freopen("C:/Users/DELL/Desktop/my.txt", "w", stdout);
sz = nowv = 1;
int n,q,x;
//scanf("%d %d",&n,&q);
read(n);read(q);
for1(i,1,n){
//scanf("%d",&x);
read(x);
update(x,root[nowv-1],root[nowv],Min,Max);//第i版本储存前i个数标记过的线段树,每次直接利用上一版本即可
//dfs(root[nowv],Min,Max);puts("");
nowv++;//不要合并98,100行为update(x,root[nowv-1],root[nowv++],Min,Max);你可以自己试试这样会咋样
}
int ql,qr,k;
while (q--){
//scanf("%d %d %d",&ql,&qr,&k);
read(ql);read(qr);read(k);
int l = Min,r = Max;
printf("%d\n",query(root[ql-1],k,root[qr],Min,Max));
}
return 0;
}