可持久化平衡树
复习了一下fhq。
和主席树类似地,可持久化数据结构的精髓在于对每次进行次数为 \(polylog\) 级别的操作进行重开点,以此用尽可能小的时空损耗来保存每次操作完的全树状态。国内常用的可持久化平衡树是fhq,容易想到地,就是将它的split和merge操作进行可持久化。
inline int merge(int x,int y){
if(!x||!y)return x^y;
if(tree[x].key<tree[y].key){
int p=newnode();
tree[p]=tree[x];
rs(p)=merge(rs(p),y);
update(p);
return p;
}
else{
int p=newnode();
tree[p]=tree[y];
ls(p)=merge(x,ls(p));
update(p);
return p;
}
}
inline void split(int p,int k,int &x,int &y){
if(!p){
x=y=0;
return ;
}
if(tree[p].val<=k){
x=newnode();
tree[x]=tree[p];
split(rs(x),k,rs(x),y);
update(x);
}
else{
y=newnode();
tree[y]=tree[p];
split(ls(y),k,x,ls(y));
update(y);
}
}
再用 \(rt\) 数组存一下每个版本的起始根就好了。
带着区间(反转)tag的平衡树要求在所有“从上到下”类型的操作中进行下传,进而把下传操作放到merge和split里,同时进行可持久化。
inline int merge(int x,int y){
if(!x||!y)return x^y;
spread(x);
spread(y);
if(tree[x].key<tree[y].key){
rs(x)=merge(rs(x),y);
update(x);
return x;
}
else{
ls(y)=merge(x,ls(y));
update(y);
return y;
}
}
inline void split(int p,ll val,int &x,int &y){
if(!p){
x=y=0;
return ;
}
spread(p);
if(tree[ls(p)].siz<val){
x=copy(p);
split(rs(x),val-tree[ls(p)].siz-1,rs(x),y);
update(x);
}
else{
y=copy(p);
split(ls(y),val,x,ls(y));
update(y);
}
}
严厉谴责学校题单前一题还在板子后一题直接拉泡大的的罪恶行为。
发现后两个操作很有想象力,而且本题以足足64mb的空间限制被放入了一个可持久化数据结构题单中,太菜了于是果断被击败并查看题解。
先不管神秘的空间限制,对于操作二,相当于是取k个元素循环地填充满区间,可以考虑倍增地merge那k个元素所属的区间直到装不下,这个时候拆一段长度匹配的散块即可,这一过程有大量的重复节点,就可以用可持久化平衡树,对于操作三,可以在一开始建立rt0的可持久化树,每次操作3直接把当前根1的那段区间赋值成0的那一部分即可。
理想很美好,但是操作2中的复制操作会复制大量同样的fhq中的随机平衡因子key,这样我们直接就平衡了个寂寞。题解提出合并时用随机值判断:让子树大小大的成为父亲的概率高一些,能相对平衡一些。
inline void split(int p,int k,int &x,int &y){
if(!p){
x=y=0;
return ;
}
if(tree[ls(p)].siz+1<=k){
x=newnode();
tree[x]=tree[p];
split(rs(x),k-tree[ls(p)].siz-1,rs(x),y);
update(x);
}
else{
y=newnode();
tree[y]=tree[p];
split(ls(y),k,x,ls(y));
update(y);
}
}
inline int merge(int x,int y){
if(!x||!y)return x^y;
if(rnd()%(tree[x].siz+tree[y].siz)<tree[x].siz){
int p=newnode();
tree[p]=tree[x];
rs(p)=merge(rs(p),y);
update(p);
return p;
}
else{
int p=newnode();
tree[p]=tree[y];
ls(p)=merge(x,ls(p));
update(p);
return p;
}
}
大概就是上面这个样子。
但是空间问题还没解决!所以提出:每当节点个数超过一半的空间最大值就直接暴力重构,这样好像很大的时间限制就说的通了。放一下全代码。
#include<bits/stdc++.h>
#define MAXN 200005
#define MAXM 2000005
#define LIM 1000000
#define ll long long
using namespace std;
int n,m,mem;
const int inf=2e9;
int a[MAXN],top;
mt19937 rnd(time(0));
struct FHQ_Treap{
#define ls(p) tree[p].lson
#define rs(p) tree[p].rson
struct node{
int lson,rson;
ll sum;
int val,siz;
}tree[MAXM];
inline void update(int p){
tree[p].siz=tree[ls(p)].siz+tree[rs(p)].siz+1;
tree[p].sum=tree[ls(p)].sum+tree[rs(p)].sum+tree[p].val;
}
int tot,rt[2];
inline int newnode(ll val=0){
tree[++tot].val=val;
tree[tot].sum=val;
tree[tot].siz=1;
ls(tot)=rs(tot)=0;
return tot;
}
inline void split(int p,int k,int &x,int &y){
if(!p){
x=y=0;
return ;
}
if(tree[ls(p)].siz+1<=k){
x=newnode();
tree[x]=tree[p];
split(rs(x),k-tree[ls(p)].siz-1,rs(x),y);
update(x);
}
else{
y=newnode();
tree[y]=tree[p];
split(ls(y),k,x,ls(y));
update(y);
}
}
inline int merge(int x,int y){
if(!x||!y)return x^y;
if(rnd()%(tree[x].siz+tree[y].siz)<tree[x].siz){
int p=newnode();
tree[p]=tree[x];
rs(p)=merge(rs(p),y);
update(p);
return p;
}
else{
int p=newnode();
tree[p]=tree[y];
ls(p)=merge(x,ls(p));
update(p);
return p;
}
}
int x,y,z,w;
inline ll query(int &p,int l,int r){
x=y=z=0;
split(p,l-1,x,y);
split(y,r-l+1,y,z);
ll res=tree[y].sum;
p=merge(x,merge(y,z));
return res;
}
inline void modify(int &p,int l,int r,int k){
x=y=z=w=0;
split(p,r,x,z);
split(x,l-k-1,x,y);
split(y,k,y,p);
int tar=r-l+1+k;
while(tree[y].siz<=tar)y=merge(y,y);
int bin=0;
split(y,r-l+1+k,y,bin);
p=merge(x,merge(y,z));
}
inline void reset(int &p,int l,int r){
x=y=z=0;
split(rt[0],l-1,x,y);
split(y,r-l+1,y,z);
x=z=0;
int bin=0;
split(p,l-1,x,bin);
split(bin,r-l+1,bin,z);
p=merge(x,merge(y,z));
}
inline void build(int l,int r,int &p){
if(l>r){
p=0;
return ;
}
int mid=l+r>>1;
p=newnode(a[mid]);
build(l,mid-1,ls(p));
build(mid+1,r,rs(p));
update(p);
}
inline void dfs(int p){
if(!p)return ;
if(ls(p))dfs(ls(p));
a[++top]=tree[p].val;
if(rs(p))dfs(rs(p));
}
inline void reconstruct(){
top=0;
dfs(rt[1]);
tot=mem;
build(1,n,rt[1]);
}
}BT;
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
BT.build(1,n,BT.rt[0]);
mem=BT.tot;
BT.rt[1]=BT.rt[0];
for(int i=1,opt,l,r,k;i<=m;i++){
scanf("%d%d%d",&opt,&l,&r);
if(opt==1)printf("%lld\n",BT.query(BT.rt[1],l,r));
else if(opt==2){
scanf("%d",&k);
BT.modify(BT.rt[1],l,r,k);
}
else BT.reset(BT.rt[1],l,r);
if(BT.tot>LIM)BT.reconstruct();
}
return 0;
}
好像这类题不是特别多,遇见了再记录吧。
可持久化Trie
这个还挺简单的,而且相当好用,主要的应用是解决区间而非全局异或问题。
这是一份可持久化01trie的板子,会主席树基本就可以自研了。
struct Trie{
#define ls(p) tree[p][0]
#define rs(p) tree[p][1]
int tree[MAXN*33][2],cnt[MAXN*33];
int tot,rt[MAXN];
inline void insert(int p,int pre,int val){//基于上个版本pre建立新版本p
for(int i=30;i>=0;i--){
cnt[p]=cnt[pre]+1;
if((val>>i)&1){
if(!rs(p))rs(p)=++tot;
ls(p)=ls(pre);
p=rs(p),pre=rs(pre);
}
else{
if(!ls(p))ls(p)=++tot;
rs(p)=rs(pre);
p=ls(p),pre=ls(pre);
}
}
cnt[p]=cnt[pre]+1;//记得跳到叶子也要加一下
}
inline int query(int x,int y,int val){//查区间lr内元素异或val的最大值
int res=0;
for(int i=30;i>=0;i--){
int k=(val>>i)&1;
if(cnt[tree[y][!k]]-cnt[tree[x][!k]]){
res+=(1<<i);
x=tree[x][!k];
y=tree[y][!k];
}
else x=tree[x][k],y=tree[y][k];
}
return res;
}
}Tr;
贪心地想:如果已经确定一个元素在这个区间是次大值,那对于这个元素肯定区间越大越好!可以单调栈跑出来每个元素左右侧第一个大于它的元素,并基于那个下标继续二分答案扩张(这样其实也就不用单调栈了),最后会生成两个它作为次大值的区间,于是对于每个 \(a_i\):
\[Ans=\max_{i=1}^{n}max(a_i\oplus f(L_{i,0},R_{i,0}),a_i\oplus f(L_{i,1},R_{i,1})) \]其中 \(f(l,r)\) 是区间 \(l,r\) 内异或 \(a_i\) 最大的值,单次可以拿01Trie在 \(O(logV)\) 复杂度求出,用可持久化Trie实现。
如果有动态添加的话,就可以直接考虑可持久化Trie了,然后观察一下询问是一个后缀和的性质,转化为求:
\[i\in[l,r],\max sum_n\oplus x\oplus sum_{i-1} \]\(sum_i\) 表示前缀异或和。其中式子前两部分是定值,第三部分用可持久化Trie求解。
\(n,m\) 都很小啊!,考虑与处理一下答案,给数列分块,设 \(sav_{l,r}\) 表示从第 \(l\) 个块到第 \(r\) 个元素间的最优解,这个是可以 \(O(n\sqrt n)\) 与处理的。对于每次询问,剩下的散块相当于询问长 \(O(\sqrt n)\) 的区间 \([l,r']\) 中的左端点和 \([l,r]\) 的右端点的最大子段异或和,忽视左右的影响,改成前缀异或和的形式然后拿可持久化Trie处理。
没啥说的,和树上主席树一个套路。
看起来很树剖啊!考虑用树剖的简化思想——dfs序解决问题,跑出dfs序然后对它建立可持久化Trie,则子树查询就是一个dfn区间,而路经查询就是若干个重链。
思路很简单但是写的时候不要犯迷糊了,转移到序列上就和树上父亲没关系了。
正在施工。
标签:持久,val,rs,int,tree,ls,数据结构 From: https://www.cnblogs.com/Claimo0611/p/18648824