[学习笔记]FHQ-Treap及其可持久化
感觉范浩强真的巨
博主只刷了模板所以就讲基础
fhq-treap
又形象的称为非旋转treap
顾名思义
保留了treap的随机数堆的特点,并以此作为复杂度正确的条件
并且所有的实现不用旋转!
思路自然,结构直观,代码简洁,理解轻松。
虽然不能支持LCT(起码我不会)
但是相较于splay可以可持久化(splay理论上旋转会造成空间爆炸)
基本和splay平分秋色,甚至更胜一筹
核心操作只有两个:
merge:把两个树合成一个树
int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; }else{ t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } }
(merge最后只有一棵树,所以可以带返回值)
需要注意的是,merge的两棵树,默认x的所有权值都小于y!
split:把一个树分裂成两个树
按照权值k分裂
void split(int now,int k,int &x,int &y){ if(!now){ x=0;y=0;return; } if(t[now].val<=k){ x=now; split(t[now].ch[1],k,t[now].ch[1],y); }else{ y=now; split(t[now].ch[0],k,x,t[now].ch[0]); } pushup(now); }
按照sz分裂:(维护序列时候)
void split(int now,int k,int &x,int &y){ if(!now) { x=0;y=0;return; } pushdown(now); if(t[t[now].ch[0]].sz+1<=k){ k-=t[t[now].ch[0]].sz+1; x=now; split(t[now].ch[1],k,t[now].ch[1],y); }else{ y=now; split(t[now].ch[0],k,x,t[now].ch[0]); } pushup(now); }
(由于split最后得到两棵树,所以只能用&)
模拟一下很直观的。
其他的操作直接看代码就可以理解:
fhq不用记录father的
#include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=100000+5; const int inf=0x3f3f3f3f; struct node{ int pri; int ch[2]; int val; int sz; }t[N]; int tot; int rt; int n; int nc(int v){ t[++tot].val=v;t[tot].sz=1; t[tot].pri=rand(); return tot; } void pushup(int x){ t[x].sz=t[t[x].ch[1]].sz+t[t[x].ch[0]].sz+1; } int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; }else{ t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } } void split(int now,int k,int &x,int &y){ if(!now){ x=0;y=0;return; } if(t[now].val<=k){ x=now; split(t[now].ch[1],k,t[now].ch[1],y); }else{ y=now; split(t[now].ch[0],k,x,t[now].ch[0]); } pushup(now); } int kth(int now,int k){ int x=now; while(1){ if(t[t[x].ch[0]].sz+1==k) return x; int tmp=t[t[x].ch[0]].sz+1; if(tmp<k){ k-=tmp;x=t[x].ch[1]; }else{ x=t[x].ch[0]; } } } int main(){ srand(19260817); rd(n); int op,x; int le=0,ri=0; while(n--){ rd(op);rd(x); switch (op){ case 1:{ split(rt,x,le,ri); rt=merge(merge(le,nc(x)),ri); break; } case 2:{ int zz; split(rt,x,le,zz); split(le,x-1,le,ri); //le=t[le].ch[0];//warning!!! maybe wrong ri=merge(t[ri].ch[0],t[ri].ch[1]); rt=merge(merge(le,ri),zz); break; } case 3:{ split(rt,x-1,le,ri); printf("%d\n",t[le].sz+1); rt=merge(le,ri); break; } case 4:{ int tmp=kth(rt,x); printf("%d\n",t[tmp].val); break; } case 5:{ split(rt,x-1,le,ri); int tmp=kth(le,t[le].sz); printf("%d\n",t[tmp].val); rt=merge(le,ri); break; } case 6:{ split(rt,x,le,ri); int tmp=kth(ri,1); printf("%d\n",t[tmp].val); rt=merge(le,ri); break; } } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/5 21:36:18 */
值得一提的是
fhq可以有重复的点
在求第k大的时候直接二分不会出问题
其他时候可能会有问题。。
所以其他时候都要split和merge
左子树,右子树的值可能存在和自己相等的情况
但是由于merge的大小一定是x小于y,所以不会出现右子树有比自己小的,左子树有比自己大的。
所以还是可以直接找第k大的。(当然按照size分裂也可以)
大概是这样吧
区间操作?
分成三棵树[1,l-1],[l,r],[r+1,n]
对于中间的树打上标记或者查询询问
然后依次merge起来
值得一提的是,可以不用加入什么0,n+1两个节点,空树在fhq-treap中不会有任何影响
(splay就有点难受了,把0splay到根会出各种各样的问题。因为0作为哨兵,father,ch都是假的)
然后就可以做文艺平衡树了:
#include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=100000+5; int n; struct node{ int rev,ch[2]; int sz; int val; int pri; }t[N]; int tot; int rt; int nc(int c){ ++tot;t[tot].val=c;t[tot].pri=rand();t[tot].sz=1; return tot; } void pushup(int x){ t[x].sz=t[t[x].ch[1]].sz+t[t[x].ch[0]].sz+1; } void rev(int x){ swap(t[x].ch[0],t[x].ch[1]); t[x].rev^=1; } void pushdown(int x){ if(t[x].rev){ rev(t[x].ch[0]);rev(t[x].ch[1]); t[x].rev=0; } } int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ pushdown(x); t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; } else{ pushdown(y); t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } } void split(int now,int k,int &x,int &y){ if(!now) { x=0;y=0;return; } pushdown(now); if(t[t[now].ch[0]].sz+1<=k){ k-=t[t[now].ch[0]].sz+1; x=now; split(t[now].ch[1],k,t[now].ch[1],y); }else{ y=now; split(t[now].ch[0],k,x,t[now].ch[0]); } pushup(now); } void op(int x){ pushdown(x); if(t[x].ch[0]) op(t[x].ch[0]); if(t[x].val>0) printf("%d ",t[x].val); if(t[x].ch[1]) op(t[x].ch[1]); } int main(){ srand(19260817); rd(n); rt=nc(-23333); for(reg i=1;i<=n;++i) rt=merge(rt,nc(i)); rt=merge(rt,nc(-66666)); int m; rd(m); int l,r; int x,y,z; while(m--){ rd(l);rd(r); split(rt,l,x,z); split(z,r-l+1,z,y); rev(z); rt=merge(merge(x,z),y); } op(rt); return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/6 8:41:45 */
最厉害的是,fhq可以可持久化!
由于出色的简单操作和结构的相对稳定性,使得一次操作不会产生太多的点
(其实splay均摊操作logn,旋转暴力建节点也是可以的吧,,但是常数和空间都大到飞起~)
总之完爆splay几条街
具体只用多几行:
split的时候,对于劈出来的这条链上每个点都建立一个新节点
merge的时候,合并出来的点也是新的节点
正确性的话,只要不会影响之前版本的查询,显然就没有问题
形成了二子多父的实际局面(和主席树也是一样的)
然后每个版本的根记录好即可。
垃圾回收还是有必要的
而且发现,merge总在split之后,split已经把新节点建好了。所以merge可以不建节点
注意第k大,保证有sz才去找,否则就RE辣。
代码:
#include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=5e5+5; const int inf=2147483647; struct node{ int val,sz; int ch[2],pri; }t[N*60]; int tot; int rt[N]; int m; int dp[N],top; int nc(int v){ int r=top?dp[top--]:++tot; t[r].val=v;t[r].sz=1; t[r].pri=rand();t[r].ch[0]=t[r].ch[1]=0; return r; } int copy(int x){ int r=top?dp[top--]:++tot; t[r]=t[x];return r; } void pushup(int x){ t[x].sz=t[t[x].ch[0]].sz+t[t[x].ch[1]].sz+1; } int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ //int now=copy(x); t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; } else{ // int now=copy(y); t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } } void split(int now,int k,int &x,int &y){ if(!now){ x=0;y=0;return; } if(t[now].val<=k){ x=copy(now); split(t[now].ch[1],k,t[x].ch[1],y); pushup(x); } else{ y=copy(now); split(t[now].ch[0],k,x,t[y].ch[0]); pushup(y); } } int kth(int now,int k){ int x=now; // cout<<" now "<<now<<" "<<t[now].sz<<" and "<<k<<endl; if(t[now].sz==0) { // cout<<" ??? "<<endl; return inf; } while(1){ // cout<<x<<endl; if(t[t[x].ch[0]].sz+1==k) return x; int tmp=t[t[x].ch[0]].sz+1; if(tmp<k) k-=tmp,x=t[x].ch[1]; else x=t[x].ch[0]; } return 23333; } int main(){ srand(19260817); int n; rd(n); int st,op,a; int x,y,z; for(reg i=1;i<=n;++i){ rd(st);rd(op);rd(a); if(op==1){ split(rt[st],a,x,y); //cout<<" after split "<<x<<" "<<y<<endl; rt[i]=merge(merge(x,nc(a)),y); //cout<<" rt "<<rt[i]<<" "<<t[rt[i]].sz<<" "<<t[rt[i]].val<<" :: "<<t[rt[i]].ch[0]<<" "<<t[rt[i]].ch[1]<<endl; }else if(op==2){ split(rt[st],a,x,y); // cout<<" after split "<<x<<" "<<y<<endl; if(t[x].sz==0||t[kth(x,t[x].sz)].val!=a) {//no exist // cout<<" no exist "<<endl; rt[i]=merge(x,y); continue; } // cout<<" after check "<<endl; split(x,a-1,x,z); //cout<<" after split2 "<<endl; z=merge(t[z].ch[0],t[z].ch[1]); //cout<<" after dele "<<endl; rt[i]=merge(merge(x,z),y); //cout<<" after merge=end"<<endl; }else if(op==3){ split(rt[st],a-1,x,y); printf("%d\n",t[x].sz+1); rt[i]=merge(x,y); }else if(op==4){ rt[i]=rt[st]; printf("%d\n",t[kth(rt[i],a)].val); }else if(op==5){ split(rt[st],a-1,x,y); if(t[x].sz==0){ printf("%d\n",-inf); }else{ printf("%d\n",t[kth(x,t[x].sz)].val); } rt[i]=merge(x,y); }else{ split(rt[st],a,x,y); if(t[y].sz==0){ printf("%d\n",inf); }else{ printf("%d\n",t[kth(y,1)].val); } rt[i]=merge(x,y); } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/6 11:11:11 */
这个模板太朴素
我第一次用主席树水过去了,还有人用可持久化0/1trie
尝试真正平衡树才能做的:
区间翻转!序列插入一个数!序列删除一个数!
然后有了此题:
区间翻转的标记下放的时候
如果有儿子,就新建一个。没有这个儿子就不用建了。空节点还是不能随便建的。否则TLE爆炸
用上垃圾回收和merge不用建立节点节省空间
就可以过啦。
#include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=2e5+5; int n; int rt[N]; struct node{ int pri,sz; int ch[2],rev; ll sum,val; }t[N*60]; int tot; int dp[N],top; int nc(int v){ int r=top?dp[top--]:++tot; t[r].val=v;t[r].sum=v;t[r].sz=1; t[r].pri=rand();t[r].ch[0]=t[r].ch[1]=0; t[r].rev=0; return r; } int cpy(int x){ int r=top?dp[top--]:++tot; t[r]=t[x];return r; } void pushup(int x){ if(!x) return; t[x].sz=t[t[x].ch[0]].sz+t[t[x].ch[1]].sz+1; t[x].sum=t[t[x].ch[0]].sum+t[t[x].ch[1]].sum+t[x].val; } void pushdown(int x){ if(!x) return; if(t[x].rev){ if(t[x].ch[1]&&t[x].ch[0]){ int ls=cpy(t[x].ch[1]),rs=cpy(t[x].ch[0]); t[x].ch[0]=ls; t[x].ch[1]=rs; t[ls].rev^=1;t[rs].rev^=1; }else if(t[x].ch[1]){ int ls=cpy(t[x].ch[1]); t[x].ch[0]=ls; t[x].ch[1]=0;t[ls].rev^=1; }else if(t[x].ch[0]){ int rs=cpy(t[x].ch[0]); t[x].ch[1]=rs; t[x].ch[0]=0;t[rs].rev^=1; } t[x].rev=0; pushup(x); } } int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ pushdown(x); //int now=cpy(x); t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; }else{ pushdown(y); //int now=cpy(y); t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } } void split(int now,int k,int &x,int &y){ //cout<<" spliting "<<now<<" k "<<k<<" :: "<<x<<" "<<y<<endl; //cout<<" infor of now "<<t[now].sz<<" "<<t[now].rev<<" || "<<t[now].val<<" "<<t[t[now].ch[0]].val<<" "<<t[t[now].ch[1]].val<<" "<<t[t[t[now].ch[1]].ch[1]].val<<" || "<<tot<<endl; if(t[now].sz==0){ x=0;y=0;return; } pushdown(now); //cout<<" after pushdown "<<t[now].sz<<" "<<t[now].rev<<" || "<<t[now].val<<" "<<t[t[now].ch[0]].val<<" "<<t[t[now].ch[1]].val<<" "<<t[t[t[now].ch[1]].ch[1]].val<<" || "<<tot<<endl; if(t[t[now].ch[0]].sz+1<=k){ k-=t[t[now].ch[0]].sz+1; x=cpy(now); split(t[now].ch[1],k,t[x].ch[1],y); pushup(x); }else{ // cout<<" cpy y"<<endl; y=cpy(now); split(t[now].ch[0],k,x,t[y].ch[0]); pushup(y); } } int main(){ srand(19260817); rd(n); ll las=0; int st,op; int p;ll a; int l,r; int x,y,z; for(reg i=1;i<=n;++i){ rd(st);rd(op); if(op==1){ scanf("%d%lld",&p,&a); p^=las;a^=las; // cout<<" insert "<<endl; // cout<<" p a "<<p<<" "<<a<<endl; split(rt[st],p,x,y); rt[i]=merge(merge(x,nc(a)),y); }else if(op==2){ scanf("%d",&p); p^=las; split(rt[st],p,x,y); split(x,p-1,x,z); dp[++top]=z; rt[i]=merge(x,y); }else if(op==3){ scanf("%d%d",&l,&r); l^=las;r^=las; // cout<<" reverse "<<endl; // cout<<" l r "<<l<<" "<<r<<endl; split(rt[st],r,x,y); split(x,l-1,x,z); z=cpy(z); t[z].rev^=1; rt[i]=merge(merge(x,z),y); }else { scanf("%d%d",&l,&r); l^=las;r^=las; // cout<<" query "<<endl; /// cout<<" l r "<<l<<" "<<r<<endl; split(rt[st],r,x,y); // cout<<" split 1 "<<endl; split(x,l-1,x,z); // cout<<" split 2 "<<endl; las=t[z].sum; printf("%lld\n",las); rt[i]=merge(merge(x,z),y); } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/6 12:20:00 */
总结:
范浩强太巨啦
简单实用,你值得拥有。