可持久化文艺平衡树

您需要写一种数据结构,来维护一个序列,其中需要提供以下操作(对于各个以往的历史版本):

  1. 在第 pp 个数后插入数 xx 。
  2. 删除第 pp 个数。
  3. 翻转区间 [l,r][l,r],例如原序列是 {5,4,3,2,1}{5,4,3,2,1},翻转区间 [2,4][2,4] 后,结果是 {5,2,3,4,1}{5,2,3,4,1}。
  4. 查询区间 [l,r][l,r] 中所有数的和。

和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本(操作 44 即保持原版本无变化),新版本即编号为此次操作的序号。

本题强制在线。

分析

函数式Treap实现。

时空复杂度\(O(n \log n)\)

  1. // luogu-judger-enable-o2
  2. typedef long long ll;
  3. co int N=2e5+7;
  4. int tot;
  5. int root[N];
  6. int can[N],cantop;
  7. namespace T
  8. {
  9. using std::swap;
  10. int ch[N<<6][2],siz[N<<6];
  11. int pri[N<<6],val[N<<6];
  12. ll sum[N<<6];
  13. bool rev[N<<6];
  14. int newnode(int v=0)
  15. {
  16. int x=cantop?can[cantop--]:++tot;
  17. ch[x][0]=ch[x][1]=0,siz[x]=1;
  18. pri[x]=rand()<<15|rand(),val[x]=sum[x]=v;
  19. rev[x]=0;
  20. return x;
  21. }
  22. int clone(int y)
  23. {
  24. int x=cantop?can[cantop--]:++tot;
  25. ch[x][0]=ch[y][0],ch[x][1]=ch[y][1],siz[x]=siz[y];
  26. pri[x]=pri[y],val[x]=val[y],sum[x]=sum[y];
  27. rev[x]=rev[y];
  28. return x;
  29. }
  30. void pushup(int x)
  31. {
  32. siz[x]=siz[ch[x][0]]+1+siz[ch[x][1]];
  33. sum[x]=sum[ch[x][0]]+val[x]+sum[ch[x][1]];
  34. }
  35. void pushdown(int x)
  36. {
  37. if(rev[x])
  38. {
  39. swap(ch[x][0],ch[x][1]);
  40. if(ch[x][0])
  41. {
  42. ch[x][0]=clone(ch[x][0]);
  43. rev[ch[x][0]]^=1;
  44. }
  45. if(ch[x][1])
  46. {
  47. ch[x][1]=clone(ch[x][1]);
  48. rev[ch[x][1]]^=1;
  49. }
  50. rev[x]=0;
  51. }
  52. }
  53. void split(int x,int k,int&l,int&r)
  54. {
  55. if(!x)
  56. {
  57. l=r=0;
  58. return;
  59. }
  60. pushdown(x);
  61. if(k<=siz[ch[x][0]])
  62. {
  63. r=clone(x);
  64. split(ch[r][0],k,l,ch[r][0]);
  65. pushup(r);
  66. }
  67. else
  68. {
  69. l=clone(x);
  70. split(ch[l][1],k-siz[ch[x][0]]-1,ch[l][1],r);
  71. pushup(l);
  72. }
  73. }
  74. int merge(int x,int y)
  75. {
  76. if(!x||!y)
  77. return x+y;
  78. if(pri[x]<pri[y])
  79. {
  80. pushdown(y);
  81. ch[y][0]=merge(x,ch[y][0]);
  82. pushup(y);
  83. return y;
  84. }
  85. else
  86. {
  87. pushdown(x);
  88. ch[x][1]=merge(ch[x][1],y);
  89. pushup(x);
  90. return x;
  91. }
  92. }
  93. void insert(int&rt,int k,int v)
  94. {
  95. int x,y;
  96. split(rt,k,x,y);
  97. rt=merge(x,merge(newnode(v),y));
  98. }
  99. void erase(int&rt,int p)
  100. {
  101. int x,y,z;
  102. split(rt,p,x,z);
  103. split(x,p-1,x,y);
  104. can[++cantop]=y;
  105. rt=merge(x,z);
  106. }
  107. void reverse(int&rt,int l,int r)
  108. {
  109. int x,y,z;
  110. split(rt,r,x,z);
  111. split(x,l-1,x,y);
  112. rev[y]^=1;
  113. rt=merge(x,merge(y,z));
  114. }
  115. ll query(int&rt,int l,int r)
  116. {
  117. int x,y,z;
  118. split(rt,r,x,z);
  119. split(x,l-1,x,y);
  120. ll ans=sum[y];
  121. rt=merge(x,merge(y,z));
  122. return ans;
  123. }
  124. }
  125. using namespace T;
  126. using namespace std;
  127. int main()
  128. {
  129. // freopen(".in","r",stdin);
  130. // freopen(".out","w",stdout);
  131. int n=read<int>();
  132. ll lastans=0;
  133. for(int i=1;i<=n;++i)
  134. {
  135. int v,q;
  136. read(v),read(q);
  137. root[i]=root[v];
  138. if(q==1)
  139. {
  140. int p,x;
  141. read(p),read(x);
  142. p^=lastans,x^=lastans;
  143. insert(root[i],p,x);
  144. }
  145. else if(q==2)
  146. {
  147. int p;
  148. read(p);
  149. p^=lastans;
  150. erase(root[i],p);
  151. }
  152. else if(q==3)
  153. {
  154. int l,r;
  155. read(l),read(r);
  156. l^=lastans,r^=lastans;
  157. reverse(root[i],l,r);
  158. }
  159. else
  160. {
  161. int l,r;
  162. read(l),read(r);
  163. l^=lastans,r^=lastans;
  164. printf("%lld\n",lastans=query(root[i],l,r));
  165. }
  166. }
  167. return 0;
  168. }

可持久化平衡树

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作(对于各个以往的历史版本):

  1. 插入x数
  2. 删除x数(若有多个相同的数,因只删除一个,如果没有请忽略该操作)
  3. 查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
  4. 查询排名为x的数
  5. 求x的前驱(前驱定义为小于x,且最大的数,如不存在输出-2147483647)
  6. 求x的后继(后继定义为大于x,且最小的数,如不存在输出2147483647)

和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本。(操作3, 4, 5, 6即保持原版本无变化)

每个版本的编号即为操作的序号(版本0即为初始状态,空树)

\(n \leq 5 \times 10^5\)

分析

可以发现非旋Treap的split和merge每次变动的都是一条链。

然后就对这条链可持久化一下就行了。

时空复杂度\(O(n \log n)\)

Hint

注意copy的部分仅限于递归处理的时候,now=0,x=0,y=0这些时候就不用可持久化了,不然会莫名其妙地错。

然后是空间问题,其实3、4、5、6操作不用可持久化,但是平衡树能A就行了,论效率平衡树肯定赶不上其他的做法。

  1. co int MAXN=5e5*50,MAXM=5e5+7; // edit 2
  2. int root[MAXN],tot;
  3. struct Treap
  4. {
  5. int ch[MAXN][2],siz[MAXN];
  6. int val[MAXN],pri[MAXN];
  7. il int newnode(rg int v=0)
  8. {
  9. ++tot;
  10. ch[tot][0]=ch[tot][1]=0,siz[tot]=1;
  11. val[tot]=v,pri[tot]=rand()|rand()<<16;
  12. return tot;
  13. }
  14. il void pushup(rg int now)
  15. {
  16. siz[now]=siz[ch[now][0]]+1+siz[ch[now][1]];
  17. }
  18. il void copy(rg int x,rg int y)
  19. {
  20. ch[x][0]=ch[y][0],ch[x][1]=ch[y][1],siz[x]=siz[y];
  21. val[x]=val[y],pri[x]=pri[y];
  22. }
  23. il void split(rg int now,rg int v,rg int&x,rg int&y)
  24. {
  25. if(!now)
  26. {
  27. x=y=0;
  28. return;
  29. }
  30. if(val[now]<=v)
  31. {
  32. x=newnode();
  33. copy(x,now);
  34. split(ch[x][1],v,ch[x][1],y);
  35. pushup(x);
  36. }
  37. else
  38. {
  39. y=newnode();
  40. copy(y,now);
  41. split(ch[y][0],v,x,ch[y][0]);
  42. pushup(y);
  43. }
  44. }
  45. il int merge(rg int x,rg int y)
  46. {
  47. if(!x||!y) // edit 1
  48. return x+y;
  49. rg int now=newnode();
  50. if(pri[x]<pri[y])
  51. {
  52. copy(now,x);
  53. ch[now][1]=merge(ch[now][1],y);
  54. pushup(now);
  55. }
  56. else
  57. {
  58. copy(now,y);
  59. ch[now][0]=merge(x,ch[now][0]);
  60. pushup(now);
  61. }
  62. return now;
  63. }
  64. il void ins(rg int&now,rg int v)
  65. {
  66. rg int x,y;
  67. split(now,v,x,y);
  68. now=merge(x,merge(newnode(v),y));
  69. }
  70. il void del(rg int&now,rg int v)
  71. {
  72. rg int x,y,z;
  73. split(now,v-1,x,y);
  74. split(y,v,y,z);
  75. y=merge(ch[y][0],ch[y][1]);
  76. now=merge(x,merge(y,z));
  77. }
  78. il int rank(rg int&now,rg int v)
  79. {
  80. rg int x,y;
  81. split(now,v-1,x,y);
  82. rg int ans=siz[x]+1;
  83. now=merge(x,y);
  84. return ans;
  85. }
  86. il int kth(rg int now,rg int k)
  87. {
  88. if(!now)
  89. return 0;
  90. while(k)
  91. {
  92. if(siz[ch[now][0]]>=k)
  93. now=ch[now][0];
  94. else if(siz[ch[now][0]]+1==k)
  95. return now;
  96. else
  97. {
  98. k-=siz[ch[now][0]]+1;
  99. now=ch[now][1];
  100. }
  101. }
  102. return now;
  103. }
  104. il int pre(rg int&now,rg int v)
  105. {
  106. rg int x,y;
  107. split(now,v-1,x,y);
  108. rg int ans=kth(x,siz[x]);
  109. now=merge(x,y);
  110. return ans;
  111. }
  112. il int suc(rg int&now,rg int v)
  113. {
  114. rg int x,y;
  115. split(now,v,x,y);
  116. rg int ans=kth(y,1);
  117. now=merge(x,y);
  118. return ans;
  119. }
  120. }T;
  121. int main()
  122. {
  123. // freopen(".in","r",stdin);
  124. // freopen(".out","w",stdout);
  125. srand(20030506);
  126. rg int n;
  127. read(n);
  128. for(rg int i=1;i<=n;++i)
  129. {
  130. rg int v,opt,x;
  131. read(v);read(opt);read(x);
  132. // cerr<<"v="<<v<<" opt="<<opt<<" x="<<x<<endl;
  133. root[i]=root[v];
  134. if(opt==1) // insert
  135. {
  136. T.ins(root[i],x);
  137. }
  138. else if(opt==2) // delete
  139. {
  140. T.del(root[i],x);
  141. }
  142. else if(opt==3) // rank
  143. {
  144. printf("%d\n",T.rank(root[i],x));
  145. }
  146. else if(opt==4) // kth
  147. {
  148. printf("%d\n",T.val[T.kth(root[i],x)]);
  149. }
  150. else if(opt==5) // pre
  151. {
  152. int ans=T.pre(root[i],x);
  153. if(ans==0)
  154. puts("-2147483647");
  155. else
  156. printf("%d\n",T.val[ans]);
  157. }
  158. else if(opt==6) // suf
  159. {
  160. int ans=T.suc(root[i],x);
  161. if(ans==0)
  162. puts("2147483647");
  163. else
  164. printf("%d\n",T.val[ans]);
  165. }
  166. }
  167. return 0;
  168. }

再分析

然而对这题而言有更优的做法,主席树(可持久化权值线段树)。

将权值离散化,得到了这题较优的做法。

但是要离线,所以也是个问题。不离线的话空间会大一些,问题不大。

时间复杂度\(O(n \log n)\),常数小多了。

Hint

注意调用查询的时候,边界问题。

另外“若有多个相同的数,因只删除一个,如果没有请忽略该操作”。这个神坑点卡了我好久,非旋式Treap会自动忽略不存在的,所以就没管。

  1. co int MAXN=5e5*20,MAXM=5e5+7;
  2. int v[MAXN],opt[MAXN],x[MAXN];
  3. vector<int>xlist;
  4. int root[MAXM],tot;
  5. struct SegTree
  6. {
  7. int sumv[MAXN];
  8. int L[MAXN],R[MAXN];
  9. il void pushup(rg int now)
  10. {
  11. sumv[now]=sumv[L[now]]+sumv[R[now]];
  12. // assert(sumv[now]>=0);
  13. }
  14. il void copy(rg int x,rg int y)
  15. {
  16. sumv[x]=sumv[y];
  17. L[x]=L[y],R[x]=R[y];
  18. }
  19. il void modify(rg int&now,rg int l,rg int r,rg int p,rg int v)
  20. {
  21. ++tot;
  22. copy(tot,now);
  23. now=tot;
  24. if(l==r)
  25. {
  26. sumv[now]+=v;
  27. return;
  28. }
  29. rg int m=(l+r)>>1;
  30. if(p<=m)
  31. modify(L[now],l,m,p,v);
  32. else
  33. modify(R[now],m+1,r,p,v);
  34. pushup(now);
  35. }
  36. il int sum(rg int now,rg int l,rg int r,rg int ql,rg int qr)
  37. {
  38. if(ql<=l&&r<=qr)
  39. return sumv[now];
  40. rg int m=(l+r)>>1;
  41. if(qr<=m)
  42. return sum(L[now],l,m,ql,qr);
  43. if(ql>=m+1)
  44. return sum(R[now],m+1,r,ql,qr);
  45. return sum(L[now],l,m,ql,qr)+sum(R[now],m+1,r,ql,qr);
  46. }
  47. il int kth(rg int now,rg int l,rg int r,rg int k)
  48. {
  49. if(l==r)
  50. return l;
  51. rg int m=(l+r)>>1;
  52. if(sumv[L[now]]>=k)
  53. return kth(L[now],l,m,k);
  54. else
  55. {
  56. k-=sumv[L[now]];
  57. return kth(R[now],m+1,r,k);
  58. }
  59. }
  60. il int pre(rg int root,rg int p)
  61. {
  62. rg int num=p>1?sum(root,1,xlist.size(),1,p-1):0; // edit 1:notice p=1
  63. if(num==0) // do not exist
  64. return 0;
  65. else
  66. return kth(root,1,xlist.size(),num);
  67. }
  68. il int suc(rg int root,rg int p)
  69. {
  70. rg int num=p<xlist.size()?sum(root,1,xlist.size(),p+1,xlist.size()):0; // edit 2:notice p=xlist.size()
  71. if(num==0) // do not exist
  72. return xlist.size()+1;
  73. else
  74. return kth(root,1,xlist.size(),sumv[root]-num+1);
  75. }
  76. }T;
  77. int main()
  78. {
  79. // freopen(".in","r",stdin);
  80. // freopen(".out","w",stdout);
  81. rg int n;
  82. read(n);
  83. for(rg int i=1;i<=n;++i)
  84. {
  85. read(v[i]);read(opt[i]);read(x[i]);
  86. if(opt[i]!=4) // unless kth
  87. xlist.push_back(x[i]);
  88. }
  89. sort(xlist.begin(),xlist.end());
  90. xlist.erase(unique(xlist.begin(),xlist.end()),xlist.end());
  91. for(rg int i=1;i<=n;++i)
  92. {
  93. if(opt[i]!=4)
  94. x[i]=lower_bound(xlist.begin(),xlist.end(),x[i])-xlist.begin()+1;
  95. // cerr<<"x "<<i<<" = "<<x[i]<<endl;
  96. root[i]=root[v[i]];
  97. if(opt[i]==1) // insert
  98. {
  99. T.modify(root[i],1,xlist.size(),x[i],1);
  100. }
  101. else if(opt[i]==2) // delete
  102. {
  103. // edit 3:if this val do not exist, you should ignore this operation
  104. if(T.sum(root[i],1,xlist.size(),x[i],x[i])==0)
  105. continue;
  106. T.modify(root[i],1,xlist.size(),x[i],-1);
  107. }
  108. else if(opt[i]==3) // rank
  109. {
  110. printf("%d\n",1+(x[i]>1?T.sum(root[i],1,xlist.size(),1,x[i]-1):0)); // edit 1: notice x[i]=1
  111. }
  112. else if(opt[i]==4) // kth
  113. {
  114. // assert(1<=x[i]&&x[i]<=T.sumv[root[i]]);
  115. printf("%d\n",xlist[T.kth(root[i],1,xlist.size(),x[i])-1]);
  116. }
  117. else if(opt[i]==5) // pre
  118. {
  119. int ans=T.pre(root[i],x[i]);
  120. if(ans!=0)
  121. printf("%d\n",xlist[ans-1]);
  122. else
  123. puts("-2147483647");
  124. }
  125. else if(opt[i]==6) // suc
  126. {
  127. int ans=T.suc(root[i],x[i]);
  128. if(ans!=xlist.size()+1)
  129. printf("%d\n",xlist[ans-1]);
  130. else
  131. puts("2147483647");
  132. }
  133. }
  134. return 0;
  135. }

三分析

其实树状数组也可以做,并且常数更小。

但是空间就必须提前开出来,并且不离线不行了。

然后不用可持久化,可以搞一个dfs。给时间点连上边,dfs的时候就修改+撤销就行了。

第一次知道这么精妙的做法,那线段树、平衡树貌似都可以这么搞,并且空间复杂度大为减小。

分享一下洛谷全站最快代码,by Mr_Spade


  1. const int N=5e5+5;
  2. int n,m,lgn;
  3. int num[N],tot;
  4. int bit[N];
  5. inline void add(int x,int k)
  6. {
  7. while(x<=n)
  8. bit[x]+=k,x+=x&-x;
  9. return;
  10. }
  11. inline int ask(int x)
  12. {
  13. int res=0;
  14. while(x)
  15. res+=bit[x],x&=x-1;
  16. return res;
  17. }
  18. inline int find(int x)
  19. {
  20. int res=0;
  21. for(int i=lgn;~i;i--)
  22. if((res|1<<i)<=n&&bit[res|1<<i]<x)
  23. x-=bit[res|=1<<i];
  24. return res+1;
  25. }
  26. struct oper
  27. {
  28. int opt,x;
  29. }o[N];
  30. int first[N],next[N];
  31. int ans[N];
  32. void dfs(int now)
  33. {
  34. int d=0;
  35. register int go;
  36. for(go=first[now];go;go=next[go])
  37. {
  38. switch(o[go].opt)
  39. {
  40. case 1:add(o[go].x,1);break;
  41. case 2:
  42. if(ask(o[go].x)^ask(o[go].x-1))
  43. add(o[go].x,-1);
  44. else
  45. d=1;
  46. break;
  47. case 3:ans[go]=ask(o[go].x-1)+1;break;
  48. case 4:ans[go]=num[find(o[go].x)];break;
  49. case 5:
  50. if(!(d=ask(o[go].x-1)))
  51. ans[go]=-0x7fffffff;
  52. else
  53. ans[go]=num[find(d)];
  54. break;
  55. case 6:
  56. if((d=ask(o[go].x))==ask(n))
  57. ans[go]=0x7fffffff;
  58. else
  59. ans[go]=num[find(d+1)];
  60. break;
  61. }
  62. dfs(go);
  63. switch(o[go].opt)
  64. {
  65. case 1:add(o[go].x,-1);break;
  66. case 2:
  67. if(!d)
  68. add(o[go].x,1);
  69. break;
  70. }
  71. }
  72. return;
  73. }
  74. signed main()
  75. {
  76. fseek(stdin,0l,2);
  77. int len=ftell(stdin);
  78. rewind(stdin);
  79. fread(in,1,len,stdin);
  80. int x;
  81. register int i;
  82. m=read();
  83. for(i=1;i<=m;i++)
  84. {
  85. next[i]=first[x=read()];first[x]=i;
  86. o[i].opt=read();o[i].x=read();
  87. if(o[i].opt^4)
  88. num[++tot]=o[i].x;
  89. }
  90. sort(num+1,num+tot+1);
  91. n=unique(num+1,num+tot+1)-num-1;
  92. for(lgn=1;1<<lgn<=n;lgn++);lgn--;
  93. for(i=1;i<=m;i++)
  94. if(o[i].opt^4)
  95. o[i].x=lower_bound(num+1,num+n+1,o[i].x)-num;
  96. dfs(0);
  97. for(i=1;i<=m;i++)
  98. if(o[i].opt!=1&&o[i].opt!=2)
  99. write(ans[i]),putchar('\n');
  100. fwrite(out,1,fout,stdout);
  101. return 0;
  102. }

这题的bug

正如上面看到的,这题可以乱搞。所以出题人要是想考察可持久化平衡树的话,最好还是强制在线,另外为了卡掉线段树还有01Trie,可以把节点弄成双键值,比如一个pair。

另外一个有趣的问题:持久化fhq treap在合并时不需要复制节点。正如我所说的:“这跟这题的单点插入有关,如果是单点插入那么相当于要插入的新节点无论如何都复制好了,换句话说隐式的新建了一个节点。但是如果是插入一段新节点的话就错了,因为这段的新节点没有隐式复制。”

讨论

文艺平衡树(Splay)

您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1

\(n,m \leq 1e5\)

分析

Splay Tree实现。

时间复杂度均摊\(O(\log n)\)

  1. typedef long long ll;
  2. co int N=1e5+7;
  3. namespace T
  4. {
  5. using std::swap;
  6. int rt;
  7. int fa[N],ch[N][2],siz[N];
  8. bool rev[N];
  9. void pushup(int x)
  10. {
  11. siz[x]=siz[ch[x][0]]+1+siz[ch[x][1]];
  12. }
  13. void pushdown(int x)
  14. {
  15. if(rev[x])
  16. {
  17. swap(ch[x][0],ch[x][1]);
  18. rev[ch[x][0]]^=1;
  19. rev[ch[x][1]]^=1;
  20. rev[x]=0;
  21. }
  22. }
  23. void rotate(int x,int&k)
  24. {
  25. int y=fa[x],z=fa[y],d=ch[y][0]==x;
  26. if(y==k)
  27. k=x;
  28. else
  29. {
  30. if(ch[z][0]==y)
  31. ch[z][0]=x;
  32. else
  33. ch[z][1]=x;
  34. }
  35. ch[y][d^1]=ch[x][d],fa[ch[y][d^1]]=y;
  36. ch[x][d]=y,fa[y]=x,fa[x]=z;
  37. pushup(x);
  38. pushup(y);
  39. }
  40. void splay(int x,int&k)
  41. {
  42. while(x!=k)
  43. {
  44. int y=fa[x],z=fa[y];
  45. if(y!=k)
  46. {
  47. if((ch[y][0]==x)^(ch[z][0]==y))
  48. rotate(x,k);
  49. else
  50. rotate(y,k);
  51. }
  52. rotate(x,k);
  53. }
  54. }
  55. void build(int l,int r,int f)
  56. {
  57. if(l>r)
  58. return;
  59. int mid=(l+r)/2;
  60. if(mid<f)
  61. ch[f][0]=mid;
  62. else
  63. ch[f][1]=mid;
  64. fa[mid]=f,siz[mid]=1;
  65. if(l==r)
  66. return;
  67. build(l,mid-1,mid);
  68. build(mid+1,r,mid);
  69. pushup(mid);
  70. }
  71. int find(int x,int k)
  72. {
  73. pushdown(x);
  74. int s=siz[ch[x][0]];
  75. if(k==s+1)
  76. return x;
  77. if(k<=s)
  78. return find(ch[x][0],k);
  79. else
  80. return find(ch[x][1],k-s-1);
  81. }
  82. void rever(int l,int r)
  83. {
  84. int x=find(rt,l),y=find(rt,r+2);
  85. splay(x,rt);
  86. splay(y,ch[x][1]);
  87. int z=ch[y][0];
  88. rev[z]^=1;
  89. }
  90. }
  91. using namespace T;
  92. using namespace std;
  93. int main()
  94. {
  95. // freopen(".in","r",stdin);
  96. // freopen(".out","w",stdout);
  97. int n,m;
  98. read(n),read(m);
  99. rt=(n+3)/2;
  100. build(1,n+2,rt);
  101. while(m--)
  102. {
  103. int l,r;
  104. read(l),read(r);
  105. rever(l,r);
  106. }
  107. for(int i=2;i<=n+1;++i)
  108. printf("%d ",find(rt,i)-1);
  109. return 0;
  110. }

普通平衡树

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入x数
  2. 删除x数(若有多个相同的数,因只删除一个)
  3. 查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
  4. 查询排名为x的数
  5. 求x的前驱(前驱定义为小于x,且最大的数)
  6. 求x的后继(后继定义为大于x,且最小的数)

\(n \leq 10^5\)

分析

用范浩强Treap实现。具体原理:





















范浩强对函数式编程在OI中的应用做了很好的引入工作。

非旋式Treap的精华在于那个merge。

merge的参数要求保证x中最大的数不大于y中最小的数。

这样在合并一个子树的时候,有两种等价情况,一种是x是y的左儿子,一种是y是x的右儿子。

选择的依据是priority,这样平衡的道理就跟普通Treap一样了。

  1. const int INF=0x7fffffff;
  2. const int MAXN=1e5+7;
  3. int sz;
  4. struct Treap
  5. {
  6. int val[MAXN],pri[MAXN];
  7. int ch[MAXN][2],siz[MAXN];
  8. void pushup(int x)
  9. {
  10. siz[x]=siz[ch[x][0]]+1+siz[ch[x][1]];
  11. }
  12. int new_node(int v)
  13. {
  14. val[++sz]=v,
  15. pri[sz]=rand()<<15|rand();
  16. ch[sz][0]=ch[sz][1]=0,
  17. siz[sz]=1;
  18. return sz;
  19. }
  20. int merge(int x,int y)
  21. {
  22. if(!x||!y)
  23. return x+y;
  24. if(pri[x]<pri[y])
  25. {
  26. ch[x][1]=merge(ch[x][1],y);
  27. pushup(x);
  28. return x;
  29. }
  30. else
  31. {
  32. ch[y][0]=merge(x,ch[y][0]);
  33. pushup(y);
  34. return y;
  35. }
  36. }
  37. void split(int now,int v,int&x,int&y)
  38. {
  39. if(!now)
  40. x=y=0;
  41. else
  42. {
  43. if(val[now]<=v)
  44. {
  45. x=now,split(ch[now][1],v,ch[now][1],y);
  46. }
  47. else
  48. {
  49. y=now,split(ch[now][0],v,x,ch[now][0]);
  50. }
  51. pushup(now);
  52. }
  53. }
  54. void ins(int&now,int v)
  55. {
  56. int x,y;
  57. split(now,v,x,y);
  58. now=merge(merge(x,new_node(v)),y);
  59. }
  60. void del(int&now,int v)
  61. {
  62. int x,y,z;
  63. split(now,v,x,z);
  64. split(x,v-1,x,y);
  65. y=merge(ch[y][0],ch[y][1]);
  66. now=merge(merge(x,y),z);
  67. }
  68. int rank(int&now,int v)
  69. {
  70. int x,y;
  71. split(now,v-1,x,y);
  72. int ans=siz[x]+1;
  73. now=merge(x,y);
  74. return ans;
  75. }
  76. int kth(int now,int k)
  77. {
  78. while(1)
  79. {
  80. if(k<=siz[ch[now][0]])
  81. now=ch[now][0];
  82. else if(k==siz[ch[now][0]]+1)
  83. return now;
  84. else
  85. k-=siz[ch[now][0]]+1,now=ch[now][1];
  86. }
  87. }
  88. int pre(int&now,int v)
  89. {
  90. int x,y;
  91. split(now,v-1,x,y);
  92. int ans=kth(x,siz[x]);
  93. now=merge(x,y);
  94. return ans;
  95. }
  96. int suc(int&now,int v)
  97. {
  98. int x,y;
  99. split(now,v,x,y);
  100. int ans=kth(y,1);
  101. now=merge(x,y);
  102. return ans;
  103. }
  104. }T;
  105. int main()
  106. {
  107. // freopen(".in","r",stdin);
  108. // freopen(".out","w",stdout);
  109. srand(20030506);
  110. int root=0;
  111. int n;
  112. read(n);
  113. while(n--)
  114. {
  115. static int opt,x;
  116. read(opt);read(x);
  117. if(opt==1) // insert
  118. {
  119. T.ins(root,x);
  120. }
  121. else if(opt==2) // delete
  122. {
  123. T.del(root,x);
  124. }
  125. else if(opt==3) // rank
  126. {
  127. printf("%d\n",T.rank(root,x));
  128. }
  129. else if(opt==4) // kth
  130. {
  131. printf("%d\n",T.val[T.kth(root,x)]);
  132. }
  133. else if(opt==5) // precursor
  134. {
  135. printf("%d\n",T.val[T.pre(root,x)]);
  136. }
  137. else // successor
  138. {
  139. printf("%d\n",T.val[T.suc(root,x)]);
  140. }
  141. }
  142. // fclose(stdin);
  143. // fclose(stdout);
  144. return 0;
  145. }

LG3835 【模板】可持久化平衡树的更多相关文章

  1. 【LG3835】可持久化平衡树

    [LG3835]可持久化平衡树 题面 洛谷 解法一 参考文章 rope大法好 \(rope\)基本操作: #include<ext/rope> using namespace __gnu_ ...

  2. luoguP3835 [模板]可持久化平衡树

    https://www.luogu.org/problemnew/show/P3835 因为博主精力和实力有限,学不懂 fhq treap 了,因此只介绍 leafy tree 解法 leafy tr ...

  3. 洛谷.3835.[模板]可持久化平衡树(fhq treap)

    题目链接 对每次Merge(),Split()时产生的节点都复制一份(其实和主席树一样).时间空间复杂度都为O(qlogq).(应该更大些 因为rand()?内存真的爆炸..) 对于无修改的操作实际上 ...

  4. luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树)(主席树)

    luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目 #include<iostream> #include<cstdlib> #include< ...

  5. Luogu P3835 【模板】可持久化平衡树(fhq Treap)

    P3835 [模板]可持久化平衡树 题意 题目背景 本题为题目普通平衡树的可持久化加强版. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作(对于各个以往的历史版本 ...

  6. [Luogu 3835]【模板】可持久化平衡树

    Description 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作(对于各个以往的历史版本): 插入x数 删除x数(若有多个相同的数,因只删除一个,如果没有请忽略该操作 ...

  7. 洛谷P3835 【模板】可持久化平衡树

    题目背景 本题为题目 普通平衡树 的可持久化加强版. 数据已经经过强化 感谢@Kelin 提供的一组hack数据 题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作( ...

  8. P3835 【模板】可持久化平衡树

    题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作(对于各个以往的历史版本): 插入x数 删除x数(若有多个相同的数,因只删除一个,如果没有请忽略该操作) 查询x数的 ...

  9. 2021.07.02 P1383 高级打字机题解(可持久化平衡树)

    2021.07.02 P1383 高级打字机题解(可持久化平衡树) 分析: 从可以不断撤销并且查询不算撤销这一骚操作可以肯定这是要咱建一棵可持久化的树(我也只会建可持久化的树,当然,还有可持久化并查集 ...

随机推荐

  1. 以普通用户启动的Vim如何保存需要root权限的文件

    在Linux上工作的朋友很可能遇到过这样一种情况,当你用Vim编辑完一个文件时,运行:wq保存退出,突然蹦出一个错误: E45: 'readonly' option is set (add ! to ...

  2. nodejs pm2配置使用

    nodejs pm2配置使用教程参考链接:http://www.111cn.net/sys/linux/100927.htm 安装步骤: yum -y install npmnpm install - ...

  3. 当vue路由变化时 改变导航条式样

    这个是导航栏: <router-link to="/unusedOrder"> <div class="">路由1</div> ...

  4. Java环境搭建---(基础)

    首先下载eclipse开发工具,下载地址:http://www.eclipse.org/downloads/,界面如下: 选择eclipse juno(4.2)的版本进入界面 点击Downloads, ...

  5. hdu3718

    题解: 见图 按照每一个位置上有相同加一 然后km 代码: #include<cstdio> #include<cmath> #include<cstring> # ...

  6. Bootstrap modal 多弹窗之叠加关闭阴影遮罩问题的解决方法

    这里也会遇到一次性关闭所有modal引起阴影遮罩的问题,也就是所有modal都关闭了,但是主页面仍然被阴影遮罩. 这个问题从哪来的,是因为modal叠加,我们点击窗口之外的空白部分,一次性关闭所有mo ...

  7. jauery-layer弹出框的使用

    一布局: <div id="detailLayer"> <div class="box-header"> <div class=& ...

  8. [置顶] kubernetes1.7新特性:PodDisruptionBudget控制器变化

    背景概念 在Kubernetes中,为了保证业务不中断或业务SLA不降级,需要将应用进行集群化部署.通过PodDisruptionBudget控制器可以设置应用POD集群处于运行状态最低个数,也可以设 ...

  9. 用两个stack实现一个队列

    class Queue { stack<int> input, output; public: void push(int x) { input.push(x); } void pop(v ...

  10. puremvc源码阅读

    1.mediator作为ui管理器,是设计成可以list多个notification 2.所有ui想要监听notification,都需要register到facade中 3.puremvc只负责消息 ...