可持久化:支持查询历史版本和在历史版本上修改
可持久化数组
主席树做即可。
可持久化并查集
主席树做即可。
要按秩合并。(路径压缩每次建logn条链,会卡爆空间MLE)
主席树节点,维护father(是一个真实下标),维护dep(集合的最大深度),
一个关键函数是query,找到代表实际位置为pos的节点的编号
对于一个版本,
合并:先找到这个两个位置的集合的根节点。
不在同一个集合里的话,就合并。
合并的时候,新建一条链,并且更新father,dep还是原来节点的dep
如果和连向的father的dep相同的话,那就把father的点的dep++,象征这个新连上的集合深度是最深深度。
(++deep的时候,可以不建立新节点。因为只是影响一些按秩合并效率,但是基本没有影响)
(upda:2019.3.5 不会影响的。因为是对新节点的deep++,和之前版本没有任何关系)
查询:直接查询即可。
#include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') #define mid ((l+r)>>1) 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=1e5+5; struct node{ int ls,rs; int fa,dep; }t[N*40]; int tot; int n,m; int las; int rt[2*N]; void build(int &x,int l,int r){ x=++tot; if(l==r) { t[x].fa=l,t[x].dep=1; return ; } build(t[x].ls,l,mid); build(t[x].rs,mid+1,r); } int query(int x,int l,int r,int to){ if(l==r) return x; if(to<=mid) return query(t[x].ls,l,mid,to); else return query(t[x].rs,mid+1,r,to); } void merge(int &x,int y,int l,int r,int to,int ff){ x=++tot; t[x].ls=t[y].ls;t[x].rs=t[y].rs; if(l==r) { t[x].fa=ff,t[x].dep=t[y].dep;return; } if(to<=mid) merge(t[x].ls,t[y].ls,l,mid,to,ff); else merge(t[x].rs,t[y].rs,mid+1,r,to,ff); } int find(int o,int to){ // cout<<" o "<<o<<" to "<<to<<endl; int now=query(rt[o],1,n,to); if(t[now].fa==to) return now; return find(o,t[now].fa); } int main(){ scanf("%d%d",&n,&m); build(rt[0],1,n); // cout<<" tot tot tot "<<tot<<endl; // for(reg i=1;i<=tot;++i){ // cout<<i<<" : "<<t[i].fa<<" "<<t[i].dep<<endl; // } int op,k,x,y;las=0; int o=0; while(m--){ rd(op); if(op==1){ ++o; rt[o]=rt[las]; rd(x);rd(y); x=find(las,x); y=find(las,y); if(t[x].fa!=t[y].fa){ if(t[x].dep>t[y].dep) swap(x,y); merge(rt[o],rt[las],1,n,t[x].fa,t[y].fa); if(t[x].dep==t[y].dep) { // cout<<" dep equal "<<t[y].fa<<endl; int lp=query(rt[o],1,n,t[y].fa); // cout<<" lplplp "<<lp<<endl; t[lp].dep++; } } las=o; }else if(op==2){ ++o; rd(k); rt[o]=rt[k]; las=k; }else{ ++o; //cout<<" las "<<las<<endl; rt[o]=rt[las]; rd(x);rd(y); //cout<<" x "<<" y "<<x<<" "<<y<<endl; x=find(las,x); y=find(las,y); //cout<<" xx "<<" yy "<<x<<" "<<y<<endl; if(t[x].fa==t[y].fa){ puts("1"); }else puts("0"); las=o; } // cout<<" tot tot tot "<<tot<<endl; // for(reg i=1;i<=tot;++i){ // cout<<i<<" : "<<t[i].fa<<" "<<t[i].dep<<endl; // } } return 0; } } int main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/11/23 7:48:57 */
不能在历史版本上更改的可持久化并查集。(也就是,历史版本形成的树是一条链)
(可持久化并茶几O(logn): (NOI2018D1T1) 每个点记录每时每刻在哪个集合里 用vector记录pair 合并的时候,启发式合并,然后暴力修改 最多O(n)个集合,每个集合记录点权最大值 查询的时候 二分找到这个时间段 查询集合点权最大值即可 )
可以做到:空间O(nlogn)时间O(nlogn)
%%ImmortalCO
可持久化平衡树:
1.还是主席树做即可。
权值暴力开到-1e9~1e9(我脑残了一下,还加了偏移量。。。)因为动态开点。。空间限制1GB
然后做就好了。
注意前驱后继的写法;
#include<bits/stdc++.h>
#define reg register int
#define il inline
#define mid (((ll)l+r)>>1)
#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=5e5+5;
const int U=2e9+1;
const int P=1e9+1;
const int inf=2147483647;
int n;
struct node{
int ls,rs,sz;
}t[40*N];
int rt[N];
int tot;
void pushup(int x){
t[x].sz=t[t[x].ls].sz+t[t[x].rs].sz;
}
void ins(int &x,int y,int l,int r,int to){
//
x=++tot;
t[x].ls=t[y].ls,t[x].rs=t[y].rs;
t[x].sz=t[y].sz+1;
if(l==r) return;
if(to<=mid) ins(t[x].ls,t[y].ls,l,mid,to);
else ins(t[x].rs,t[y].rs,mid+1,r,to);
}
void dele(int &x,int y,int l,int r,int to){
// cout<<" deleting "<<to<<endl;
x=++tot;
t[x].ls=t[y].ls,t[x].rs=t[y].rs;
t[x].sz=t[y].sz;
if(l==r){
if(t[x].sz>=1) t[x].sz--;
return;
}
if(to<=mid) dele(t[x].ls,t[y].ls,l,mid,to);
else dele(t[x].rs,t[y].rs,mid+1,r,to);
pushup(x);
}
int rk(int x,int l,int r,int c){
if(l==r){
return (l<c)*t[x].sz;
}
if(c<=mid) return rk(t[x].ls,l,mid,c);
else return t[t[x].ls].sz+rk(t[x].rs,mid+1,r,c);
}
int kth(int x,int l,int r,int k){
//cout<<l<<" "<<r<<" "<<mid<<" kkk "<<k<<" "<<t[x].sz<<endl;
if(l==r)return l;
int d=k-t[t[x].ls].sz;
if(d<=0) return kth(t[x].ls,l,mid,k);
else return kth(t[x].rs,mid+1,r,d);
}
int pre(int x,int l,int r,int c){
if(l>=c||t[x].sz==0) return -inf;
else if(l==r) return l;
else{
int ret=pre(t[x].rs,mid+1,r,c);
if(ret!=-inf) return ret;
return pre(t[x].ls,l,mid,c);
}
}
int bac(int x,int l,int r,int c){
//cout<<l<<" "<<r<<" "<<" : "<<t[x].sz<<endl;
if(r<=c||t[x].sz==0) return inf;
else if(l==r) return l;
else{
int ret=bac(t[x].ls,l,mid,c);
if(ret!=inf) return ret;
return bac(t[x].rs,mid+1,r,c);
}
}
int main(){
scanf("%d",&n);
int st,op,x;
int o=0;
while(n--){
rd(st),rd(op);rd(x);
x+=P;
++o;
rt[o]=rt[st];
switch(op){
case 1:ins(rt[o],rt[st],1,U,x);break;
case 2:dele(rt[o],rt[st],1,U,x);break;
case 3:printf("%d
",rk(rt[o],1,U,x)+1);break;
case 4:{
int tmp=kth(rt[o],1,U,x-P);
//cout<<" tmp "<<tmp<<" "<<tmp-1e9<<" "<<tmp-1e9-1<<endl;
printf("%d
",tmp-P);
break;
}
case 5:{
int tmp=pre(rt[o],1,U,x);
if(tmp>=1&&tmp<=U){
printf("%d
",tmp-P);
}
else printf("%d
",tmp);//not find
break;
}
case 6:{
int tmp=bac(rt[o],1,U,x);
if(tmp>=1&&tmp<=U){
printf("%d
",tmp-P);
}
else printf("%d
",tmp);//not find
break;
}
}
//cout<<" num "<<o<<" : "<<" tot "<<tot<<" sz "<<rt[o]<<" "<<t[rt[o]].sz<<endl;
}
return 0;
}
}
int main(){
Miracle::main();
return 0;
}
/*
Author: *Miracle*
Date: 2018/11/23 9:19:03
*/
2.fhq-Treap?
留坑
可持久化0/1Trie
其实就类似于主席树。(哪里都是主席树啊。。。)
维护一个序列前缀的信息。
每次加入一个点,在前一个的基础上,加入的是一个log(val)的链。
额外维护一个sz,表示,前i个位置,走到这个位置,往下还有多少个数。(就类似于主席树)
然后,给一个x,如果要找区间最大异或值,直接sz差分,判断有无,然后贪心走即可。
例题:模板:最大异或和
变一下形,就可以当“给一个x,找区间一个值异或,使得值最大”
#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=300000+5; const int U=23; int n,m; struct trie{ int ch[2]; int sz; }t[48*N]; int tot; int s; int rt[N+N]; void ins(int id,int v){ rt[id]=++tot; int x=rt[id],y=rt[id-1]; for(reg i=U;i>=0;--i){ int c=(v>>i)&1; t[x].ch[!c]=t[y].ch[!c]; t[x].ch[c]=++tot; x=t[x].ch[c]; y=t[y].ch[c]; t[x].sz=t[y].sz+1; } } int query(int l,int r,int v){ int y=l-1>=0?rt[l-1]:rt[0],x=rt[r]; int ret=0; for(reg i=U;i>=0;--i){ int c=(v>>i)&1; int d=t[t[x].ch[!c]].sz-t[t[y].ch[!c]].sz; if(d){ ret+=(1<<i); x=t[x].ch[!c];y=t[y].ch[!c]; } else{ x=t[x].ch[c];y=t[y].ch[c]; } } //cout<<l<<" "<<r<<" "<<x<<endl; if(l==0) ret=max(ret,v); return ret; } int main(){ scanf("%d%d",&n,&m); int x; for(reg i=1;i<=n;++i){ rd(x),s^=x,ins(i,s); } char ch[10];int l,r; int now=n; while(m--){ scanf("%s",ch+1); //cout<<"ss "<<s<<endl; switch(ch[1]){ case 'A':rd(x);s^=x;ins(++now,s);break; case 'Q':{ rd(l);rd(r);rd(x); --l,--r; x=s^x; printf("%d ",query(l,r,x)); break; } } //cout<<" mmm "<<m<<endl; } return 0; } } int main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/11/23 14:22:23 */
维护两个可持久化Trie,一个dfn序,处理子树。一个维护到树根的信息。
子树,dfn序直接查询
路径,拆成x到lca,y到lca分别差分查询。
注意数组大小,根节点还有2*N个空间。。。。。
31*N*2+2*N
#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; struct trie{ int ch[2]; int sz; }t[31*N*2+2*N]; int tot; int df,dfn[N],fdfn[N],dfn2[N]; int fa[N][20]; int dep[N]; int a[N]; int n,m; struct node{ int nxt,to; }e[2*N]; int hd[N],cnt; void add(int x,int y){ e[++cnt].nxt=hd[x]; e[cnt].to=y; hd[x]=cnt; } int rt1[N]; int lca(int x,int y){ if(dep[x]<dep[y]) swap(x,y); for(reg j=19;j>=0;--j){ if(dep[fa[x][j]]>=dep[y]) x=fa[x][j]; } if(x==y) return x; for(reg j=19;j>=0;--j){ if(fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j]; } return fa[x][0]; } void ins1(int x,int y,int v){ rt1[x]=++tot; x=rt1[x]; for(reg i=30;i>=0;--i){ int c=(v>>i)&1; t[x].ch[!c]=t[y].ch[!c]; t[x].ch[c]=++tot; x=t[x].ch[c]; y=t[y].ch[c]; t[x].sz=t[y].sz+1; } } void dfs(int x,int d){ dep[x]=d; dfn[x]=++df;fdfn[df]=x; ins1(x,rt1[fa[x][0]],a[x]); for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa[x][0]) continue; fa[y][0]=x; dfs(y,d+1); } dfn2[x]=df; } int query1(int y,int x,int v){ x=rt1[x];y=rt1[y]; int ret=0; for(reg i=30;i>=0;--i){ int c=(v>>i)&1; int d=t[t[x].ch[!c]].sz-t[t[y].ch[!c]].sz; if(d){ ret+=(1<<i); x=t[x].ch[!c];y=t[y].ch[!c]; } else { x=t[x].ch[c];y=t[y].ch[c]; } } return ret; } int rt2[N]; void ins2(int x,int v){ int y=rt2[x-1]; rt2[x]=++tot; x=rt2[x]; for(reg i=30;i>=0;--i){ int c=(v>>i)&1; t[x].ch[!c]=t[y].ch[!c]; t[x].ch[c]=++tot; x=t[x].ch[c]; y=t[y].ch[c]; t[x].sz=t[y].sz+1; } } int query2(int y,int x,int v){ x=rt2[x];y=rt2[y]; int ret=0; for(reg i=30;i>=0;--i){ int c=(v>>i)&1; int d=t[t[x].ch[!c]].sz-t[t[y].ch[!c]].sz; if(d){ ret+=(1<<i); x=t[x].ch[!c];y=t[y].ch[!c]; } else { x=t[x].ch[c];y=t[y].ch[c]; } } return ret; } int main(){ scanf("%d%d",&n,&m); for(reg i=1;i<=n;++i){ rd(a[i]); } int x,y; for(reg i=1;i<=n-1;++i){ rd(x);rd(y);add(x,y);add(y,x); } dep[0]=-1; dfs(1,1); for(reg j=1;j<=19;++j){ for(reg i=1;i<=n;++i){ fa[i][j]=fa[fa[i][j-1]][j-1]; } } for(reg i=1;i<=n;++i){ ins2(i,a[fdfn[i]]); } int op; int z; while(m--){ scanf("%d",&op); if(op==1){ rd(x);rd(y); printf("%d ",query2(dfn[x]-1,dfn2[x],y)); } else{ rd(x);rd(y);rd(z); int anc=lca(x,y); printf("%d ",max(query1(fa[anc][0],x,z),query1(fa[anc][0],y,z))); } } return 0; } } int main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/11/23 16:08:01 */
可持久化用途
可持久化目的主要就是充分利用不会动的信息,减少时空的浪费
1.历史值查询:模板,以及可持久化trie和主席树的差分
2.路径压缩,任意字符集AC自动机
3.当做标记:bzoj3946: 无聊的游戏