主席树(基础)


不断开辟新的节点,存储新状态下改变的点,不改变的点直接回历史中去找,相当于新存变化的节点,不变的节点共用。

可持久化线段树 1

P3919 【模板】可持久化线段树 1(可持久化数组)

时间数组下标维护的是根节点。在根节点建树,不变的部分直接指向原先的子树即可。

代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<vector>
#include<random>
#include<ctime>
using namespace std;
long long r_r(){//快读 
	long long k=0,f=1;
	char c=getchar();
	while(!isdigit(c)){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(isdigit(c)){
		k=(k<<1)+(k<<3)+(c^48);
		c=getchar();
	}
	return k*f;
}
const int o_o=1e6+10;
int a_a[o_o],n,q,a_s[o_o<<5],m;
struct po{
	int l,r;//左右儿子 
	int v;//节点价值 
}t_r[o_o<<5];
int x_p;
void b_t(int &k,int l,int r){
	k=++x_p;//分配节点编号 
	if(l==r){//是子叶 
		t_r[k].v=a_a[l];//节点赋值 
		return;
	}
	int m_i=(l+r)>>1;
	b_t(t_r[k].l,l,m_i);//左子树 
	b_t(t_r[k].r,m_i+1,r);//右子树 
}
void g_a(int &k,int l_s,int l,int r,int q,int v){
	k=++x_p;//分配新节点 
	t_r[k].l=t_r[l_s].l;//继承历史节点信息 
	t_r[k].r=t_r[l_s].r;
	t_r[k].v=t_r[l_s].v;
	if(l==r){//是叶节点 
		t_r[k].v=v;//更改新值 
		return;
	}
	int m_i=(l+r)>>1;
	if(q<=m_i)g_a(t_r[k].l,t_r[l_s].l,l,m_i,q,v);//目标点在左子树 
	else g_a(t_r[k].r,t_r[l_s].r,m_i+1,r,q,v);//目标点在右子树 
}
int f_i(int k,int l,int r,int q){
	if(l==r)return t_r[k].v;//找到叶节点 
	int m_i=(l+r)>>1;
	if(q<=m_i)return f_i(t_r[k].l,l,m_i,q);//目标节点在左子树 
	else return f_i(t_r[k].r,m_i+1,r,q);//目标节点在右子树 
}
int main(){
	n=r_r();m=r_r();
	for(int i=1;i<=n;i++)a_a[i]=r_r();//读入原序列 
	b_t(a_s[0],1,n);//建树(0 的历史状态根节点为 0) 
	for(int i=1;i<=m;i++){
		int l_s=r_r(),op=r_r(),x=r_r();
		if(op==1){
			int v=r_r();
			
			//当前值,历史值,左端点,右端点,要改的位置,改成的值 
			g_a(a_s[i],a_s[l_s],1,n,x,v);//修改值 
		}
		if(op==2){
			printf("%d\n",f_i(a_s[l_s],1,n,x));//查询节点信息 
			a_s[i]=a_s[l_s];//直接复制历史信息(完全一样的版本) 
		}
	}
	return 0;
}

可持久化线段树 2

P3834 【模板】可持久化线段树 2

每插入一个数,存一次线段树的状态。时间变成了插入的顺序,这样查 $[l,r]$ 区间第 $k$ 大时,找到 $l-1$,$r$ 为根的线段树,相减,剩下的树中的数就是 $[l,r]$ 中的所有数。直接查排名即可。(插入数的时候要根据平衡树的特性插,这样查询的时候,就是平衡树查排名。)

注意:由于本题的值域过大,所以要离散化。

代码

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<set>
#include<queue>
#include<vector>
#include<bitset>
#include<map>
#include<utility>
#include<random>
#include<ctime> 
using namespace std;
int r_r(){//快读 
	int x=0,f=1;
	char c=getchar();
	while(!isdigit(c)){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(isdigit(c)){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}
const int o_o=1e6+10;
int x_p;//节点编号 
int n,m;
struct po{
	int s_m;//子树大小 
	int l,r;//左右节点 
}t_r[o_o<<5];
int g_g[o_o];//每个时刻线段树的根节点 
int a_a[o_o],s_a[o_o];//原序列,离散化后的序列 
void b_t(int &k,int l,int r){ 
	k=++x_p;//新节点编号 
	if(l==r)return;//叶子结点返回 
	int m_i=(l+r)>>1;
	b_t(t_r[k].l,l,m_i);//左子树 
	b_t(t_r[k].r,m_i+1,r);//右子树 
}
int a_d(int k,int l,int r,int x_x){
	int n=++x_p;//新节点标号 
	t_r[n].l=t_r[k].l;//记录上个状态左右儿子 
	t_r[n].r=t_r[k].r;//记录上个状态左右儿子 
	t_r[n].s_m=t_r[k].s_m+1;//每次比上次多加一个点 
	if(l==r)return n;//叶子结点,没有子树返回 
	int m_i=(l+r)>>1;
	if(x_x<=m_i)t_r[n].l=a_d(t_r[n].l,l,m_i,x_x);//目标在左子树 
	else t_r[n].r=a_d(t_r[n].r,m_i+1,r,x_x);//目标在右子树 
	return n;//返回节点编号 
}
int f_i(int t_1,int t_2,int l,int r,int k){
	int a_s;
	int m_i=((l+r)>>1);
	int x=t_r[t_r[t_2].l].s_m-t_r[t_r[t_1].l].s_m;//从 t_2 状态减去 t_1-1 后,剩下的元素直接查排名即可 
	if(l==r)return l;//叶子结点,找到目标,返回 
	if(x>=k)a_s=f_i(t_r[t_1].l,t_r[t_2].l,l,m_i,k);//在左子树中 
	else a_s=f_i(t_r[t_1].r,t_r[t_2].r,m_i+1,r,k-x);//在右子树中 
	return a_s;//返回结果 
}
int main(){
	n=r_r(),m=r_r();
	for(int i=1;i<=n;i++){
		a_a[i]=r_r();//读入原序列 
		s_a[i]=a_a[i];//初始化离散序列 
	}
	sort(s_a+1,s_a+n+1);//离散序列排序 
	int l_n=unique(s_a+1,s_a+n+1)-s_a-1;//去重后的长度 
	b_t(g_g[0],1,l_n);//建树 
	for(int i=1;i<=n;i++){
		int x_x=lower_bound(s_a+1,s_a+l_n+1,a_a[i])-s_a;//当前数在整个序列中的排名 
		g_g[i]=a_d(g_g[i-1],1,l_n,x_x);//存储更节点编号 
	}
	while(m--){//询问 
		int l=r_r(),r=r_r(),k=r_r();
		int a_s=f_i(g_g[l-1],g_g[r],1,l_n,k);//查寻结果 
		printf("%d\n",s_a[a_s]);//在离散化后的数组中找答案 
	}
	return 0;
}

高级打字机

P1383 高级打字机

存每一个字符插入后的变化新树,回撤操作直接访问里历史根节点即可,查找字符直接找排名找值即可。

代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<vector>
#include<random>
#include<ctime>
using namespace std;
long long r_r(){
	long long k=0,f=1;
	char c=getchar();
	while(!isdigit(c)){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(isdigit(c)){
		k=(k<<1)+(k<<3)+(c^48);
		c=getchar();
	}
	return k*f;
}
const int o_o=1e6+10;
int m,x_p=1,z_g;
int g_g[o_o];
struct po{
	int l;
	int r;
	char v;
	int s_z;
}t_r[o_o<<2];
void u_p(int k){//更新子树大小 
	t_r[k].s_z=t_r[t_r[k].l].s_z+t_r[t_r[k].r].s_z;
}
void c_a(int &k,int l_s,int l,int r,char x){
	if(l>r) return;//超出范围,退出 
	k=++x_p;//变新节点 
	t_r[k].l=t_r[l_s].l;//继承这个节点的状态 
	t_r[k].r=t_r[l_s].r;
	t_r[k].s_z=t_r[l_s].s_z;
	t_r[k].v=t_r[l_s].v;
	if(l==r){//子叶,记录新字符状态 
		t_r[k].v=x;//记录新字符 
		t_r[k].s_z=1;//更新子树大小 
		return;
	}
	int m_i=(l+r)>>1;
	if(t_r[t_r[k].l].s_z==m_i-l+1)//左子树满了 
		c_a(t_r[k].r,t_r[l_s].r,m_i+1,r,x);//插到右子树 
	else c_a(t_r[k].l,t_r[l_s].l,l,m_i,x);//左子树没满 
	u_p(k);//更新节点信息 
}
char g_a(int k,int l,int r,int x){//在当前子树中找排名 
	if(l==r)return t_r[k].v;//找到节点 
	int m_i=(l+r)>>1; 
	if(x<=t_r[t_r[k].l].s_z)//左子树能包含这种情况 
		return g_a(t_r[k].l,l,m_i,x);
	else//在右子树找 
		return g_a(t_r[k].r,m_i+1,r,x-t_r[t_r[k].l].s_z);
}
int main(){
	m=r_r();
	for(int i=1;i<=m;i++){
		char op,c;
		int x;
		cin>>op;
		if(op=='T'){
			cin>>c;
			++z_g;//更新总操作数 
			c_a(g_g[z_g],g_g[z_g-1],1,m,c);//插入字符 
		}else if(op=='U'){
			x=r_r();
			++z_g;//更新总操作数 
			g_g[z_g]=g_g[z_g-x-1];//返回历史状态 
		}else if(op=='Q'){
			x=r_r();
			cout<<g_a(g_g[z_g],1,m,x)<<endl;//输出当前转态字符 
		}
	}
	return 0;
}

文章作者: 王大神——A001
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 王大神——A001 !
  目录