可持久化数据结构——学习笔记

可持久化前提:

本身的拓扑结构不改变 (平衡树左右旋,拓扑结构改变,不可持久化)

核心思想:

存下数据结构的所有历史版本时,只记录每个版本与前一个版本 改变的部分,每次新增部分至多log(n)个节点,m次为mlog(n)

可持久化trie:

算法步骤:

1、插入:枚举str的每一位,当前位s[i]新建一个节点,当前位的其余字符都与上一版本相同,直接copy
2、查询:查询(l,r)的某个信息时,利用历史版本的可减性,( r ) - ( l-1 )得到区间(l,r)的信息

P4735 最大异或和

题意:在前缀和{S[i]}中,找到一个 S[pos],求与常数C的异或最大值
限制 L <= pos <= R
如果只限制(1,R),直接可持久化trie,记录历史版本即可
如果再增加下标的下界限制:
法一:预处理trie中每个子树的mxid (可递归实现)
法二:预处理trie中每个子树中 数的个数cnt,再利用可持久化trie的可减性得到区间(l,r)的个数,每次贪心找 当前位相反且个数不为0的数

代码:

const int N=6e5+10;
int pre[N];
int tr[N*25][2], cnt[N*25];//0~23, 24位,24条边,25个点 
int root[N],idx;

/* 
void insert(int q,int p,int i,int k){
	if(k<0){ cnt[q]=cnt[p] +1; return ; }//初态,注意要先copy上一版本p的cnt,再+1 
	
	int v=(pre[i]>>k)&1;
	if(p) tr[q][v^1]=tr[p][v^1];//copy
	
	tr[q][v]=++idx;
	insert(tr[q][v],tr[p][v], i,k-1);
	cnt[q]=cnt[tr[q][0]] + cnt[tr[q][1]]; //计算trie子树中 数字个数 
}*/ 

void insert(int q,int p,int val){
	fd(i,23,0){
		int x=(val>>i)&1;
		tr[q][x^1]=tr[p][x^1];//
		
		cnt[q]=cnt[p];//copy
		cnt[q]+=1;
		
		tr[q][x]=++idx;
		q=tr[q][x], p=tr[p][x];
	}
	
	cnt[q]=cnt[p] +1;//特别注意:最后的叶子也要更新cnt 
}

int query(int q,int p, int C){
	int res=0;
	fd(i,23,0){
		int v=(C>>i)&1;
		if( tr[q][v^1] && cnt[tr[q][v^1]] - cnt[tr[p][v^1]]>0 ) q=tr[q][v^1], p=tr[p][v^1],	res|=(1<<i);
		else q=tr[q][v], p=tr[p][v];
	}
	
	return res;
}

int Work(){
	pre[0]=0;
	int n,q; scanf("%d%d",&n,&q);
	fu(i,1,n) {	
		int x; scanf("%d",&x);
		pre[i]=pre[i-1]^x;
	}
	
//	root[0]=++idx; insert(root[0],0, 0,23);
//	fu(i,1,n) root[i]=++idx, insert(root[i],root[i-1], i,23); 
	
	root[0]=++idx, insert(root[0],0, pre[0]);
	fu(i,1,n) root[i]=++idx, insert(root[i],root[i-1], pre[i]);
	
	while(q--){
		char op[2]; scanf("%s",op);
		if(op[0]=='A'){
			int x; scanf("%d",&x);
			++n; pre[n]=pre[n-1]^x;
			
//			root[n]=++idx, insert(root[n],root[n-1], n,23);
			root[n]=++idx, insert(root[n],root[n-1], pre[n]);
		}
		else {
			int l,r,x; scanf("%d%d%d",&l,&r,&x);
			if(l-2<0) printf("%d\n",query(root[r-1],0, pre[n]^x) );
			else printf("%d\n",query(root[r-1],root[l-2], pre[n]^x) );//防越界,	p-1 (l-1,r-1)
		}
	} 
	return 0;
}

可持久化线段树 / 主席树

解决区间(l,r)中 介于(x,y)的问题

算法步骤:

1、 建树,动态开点
2、 插入:与可持久化trie类似,相同的直接copy,只需建立新增部分
3、 查询:利用可减性计算区间(l,r)的信息

P3834 【模板】可持久化线段树 2(主席树)

题意:求区间(l,r) 第k小数

代码:


const int N=2e5+10;
int a[N],A[N],tot;
struct node{
	int l,r; int cnt;
}t[N*4 +N*18];// m次修改,空间加 mlog(n)
int root[N],idx;


void up(int q){ t[q].cnt=t[t[q].l].cnt + t[t[q].r].cnt; }

int build(int l,int r){//新建区间(l,r)的节点q
	int q=++idx;
	if(l==r) return q; //叶子 
	
	int mid=(l+r)>>1;
	t[q].l=build(l,mid), t[q].r=build(mid+1,r);
	return q; 
}
int insert(int p,int l,int r, int val){//在区间(l,r)找val的位置,插入val	(离散化后,pos=val) 
	int q=++idx;
	t[q]=t[p];//完全copy
	if(l==r){ t[q].cnt+=1; return q; }
	
	int mid=(l+r)>>1;
	if(val<=mid) t[q].l=insert(t[p].l, l,mid,val);
	else t[q].r=insert(t[p].r, mid+1,r,val);
	
	up(q); 
	return q;//返回当前插入后的 新版本q 
}

int query(int q,int p,int l,int r,int k){
	if(l==r) return l;
	
	int cnt=t[t[q].l].cnt - t[t[p].l].cnt;//root[r]版本的左子树中 下标在(l,r)的个数 
	int mid=(l+r)>>1;
	if(k<=cnt) return query(t[q].l,t[p].l, l,mid,k);
	else return query(t[q].r,t[p].r, mid+1,r,k-cnt);
}

int getid(int val){
	return lower_bound(A+1,A+1+tot,val) -A;
}

int Work(){
	tot=idx=0;
	int n,q; cin>>n>>q;
	fu(i,1,n) scanf("%d",a+i), A[++tot]=a[i];
	sort(A+1,A+1+tot);
	tot=unique(A+1,A+1+tot)-(A+1);
	
	root[0]=build(1,tot);	//初始建树 
	
	fu(i,1,n) root[i]=insert(root[i-1],1,tot, getid(a[i]));//每次修改插入一个数,返回新的根节点q 
	
	
	while(q--){
		int l,r,k; scanf("%d%d%d",&l,&r,&k);
		int id=query(root[r],root[l-1],1,tot,k);
		printf("%d\n",A[id]);
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值