飞道的博客

北京化工大学1/17寒假集训题解(>1800)

411人阅读  评论(0)

 

目录

A - 文艺平衡树

B - 可持久化文艺平衡树

C - 可持久化平衡树

主要思路:FHQ Treap + 可持久化

D - 维护数列

初始化

Insert操作

Delete操作

Reverse操作

Make-Same操作

Get-Sum操作

Max-Sum操作

懒标记的处理

E - 文本编辑器


A - 文艺平衡树

这里的Splay维护是按照的是序列中的编号排序

那么,继续考虑,其实最终的结果也就是整颗Splay的中序遍历(平衡树的性质诶)

那么,现在如果按照权值来维护显然是不正确的

继续找找规律,发现,如果一个点在序列中的位置为第K个

那么,他就是平衡树的第K大(就当做普通的Splay来看的话)

所以,序列中的位置就变成了区间的第K大点

继续考虑如何翻转

翻转也就是整颗子树的每一个节点的左右儿子交换

因此,只要在根节点的地方打一个标记

在旋转之前下方一下标记就行了

最后输出的时候输出的就是Splay的中序遍历

至于初始的Splay怎么建立,可以直接构造完美的Splay

像我这种比较懒得,直接弄了一个insert。。。


  
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstdlib>
  4. #include<cstring>
  5. #include<cmath>
  6. #include<algorithm>
  7. using namespace std;
  8. #define MAX 200000
  9. inline int read()
  10. {
  11. int x= 0,t= 1; char ch= getchar();
  12. while((ch< '0'||ch> '9')&&ch!= '-')ch= getchar();
  13. if(ch== '-')t= -1,ch= getchar();
  14. while(ch<= '9'&&ch>= '0')x=x* 10+ch -48,ch= getchar();
  15. return x*t;
  16. }
  17. struct Node
  18. {
  19. int ch[ 2];
  20. int ff,v;
  21. int size;
  22. int mark;
  23. void init(int x,int fa)
  24. {
  25. ff=ch[ 0]=ch[ 1]= 0;
  26. size= 1;v=x;ff=fa;
  27. }
  28. }t[MAX];
  29. int N,root,M,tot;
  30. inline void pushup(int x)
  31. {
  32. t[x].size=t[t[x].ch[ 0]].size+t[t[x].ch[ 1]].size+ 1;
  33. }
  34. inline void pushdown(int x)
  35. {
  36. if(t[x].mark)
  37. {
  38. t[t[x].ch[ 0]].mark^= 1;
  39. t[t[x].ch[ 1]].mark^= 1;
  40. t[x].mark= 0;
  41. swap(t[x].ch[ 0],t[x].ch[ 1]);
  42. }
  43. }
  44. inline void rotate(int x)
  45. {
  46. int y=t[x].ff;
  47. int z=t[y].ff;
  48. int k=t[y].ch[ 1]==x;
  49. t[z].ch[t[z].ch[ 1]==y]=x;
  50. t[x].ff=z;
  51. t[y].ch[k]=t[x].ch[k^ 1];
  52. t[t[x].ch[k^ 1]].ff=y;
  53. t[x].ch[k^ 1]=y;
  54. t[y].ff=x;
  55. pushup(y); pushup(x);
  56. }
  57. inline void Splay(int x,int goal)
  58. {
  59. while(t[x].ff!=goal)
  60. {
  61. int y=t[x].ff; int z=t[y].ff;
  62. if(z!=goal)
  63. (t[z].ch[ 1]==y)^(t[y].ch[ 1]==x)? rotate(x): rotate(y);
  64. rotate(x);
  65. }
  66. if(goal== 0)root=x;
  67. }
  68. inline void insert(int x)
  69. {
  70. int u=root,ff= 0;
  71. while(u)ff=u,u=t[u].ch[x>t[u].v];
  72. u=++tot;
  73. if(ff)t[ff].ch[x>t[ff].v]=u;
  74. t[u]. init(x,ff);
  75. Splay(u, 0);
  76. }
  77. inline int Kth(int k)
  78. {
  79. int u=root;
  80. while( 233)
  81. {
  82. pushdown(u);
  83. if(t[t[u].ch[ 0]].size>=k)u=t[u].ch[ 0];
  84. else if(t[t[u].ch[ 0]].size+ 1==k) return u;
  85. else k-=t[t[u].ch[ 0]].size+ 1,u=t[u].ch[ 1];
  86. }
  87. }
  88. void write(int u)
  89. {
  90. pushdown(u);
  91. if(t[u].ch[ 0]) write(t[u].ch[ 0]);
  92. if(t[u].v> 1&&t[u].v<N+ 2) printf( "%d ",t[u].v -1);
  93. if(t[u].ch[ 1]) write(t[u].ch[ 1]);
  94. }
  95. inline void Work(int l,int r)
  96. {
  97. l= Kth(l);
  98. r= Kth(r+ 2);
  99. Splay(l, 0);
  100. Splay(r,l);
  101. t[t[t[root].ch[ 1]].ch[ 0]].mark^= 1;
  102. }
  103. int main()
  104. {
  105. N= read();M= read();
  106. for( int i= 1;i<=N+ 2;++i) insert(i);
  107. while(M--)
  108. {
  109. int l= read(),r= read();
  110. Work(l,r);
  111. }
  112. write(root);
  113. printf( "\n");
  114. return 0;
  115. }

B - 可持久化文艺平衡树

FHQ Treap其实是一种加强版的Treap。与一般的Treap树不同,FHQ Treap不依赖旋转操作保持自身结构的平衡,而是依赖分裂合并操作维持树的平衡性质。
我们先来介绍一下关键操作:

 

代码如下


  
  1. #include<cstdio>
  2. #include<cstring>
  3. #include<cmath>
  4. #include<climits>
  5. #include<cstdlib>
  6. #include<ctime>
  7. #include<algorithm>
  8. #include<complex>
  9. #include<iostream>
  10. #include<map>
  11. #include<queue>
  12. #include<vector>
  13. #define ll long long
  14. #define INF 0x3f3f3f3f
  15. #define ls(p) tree[p].lson
  16. #define rs(p) tree[p].rson
  17. #define tls(p) tree[ls(p)]
  18. #define trs(p) tree[rs(p)]
  19. #define t(p) tree[p]
  20. #define tpi t(++tot)
  21. #define tp t(tot)
  22. using namespace std;
  23. const int N(2e5);
  24. int n;ll lastans;
  25. struct node
  26. {
  27. int rand,size,tag;
  28. ll val,sum;
  29. int lson,rson;
  30. }tree[(N<< 7)+ 10];
  31. int rt[N+ 10];
  32. inline int new_node(long long v=0)
  33. {
  34. static int tot(0);
  35. tpi.val=v;tp.sum=v;
  36. tp.rand= rand();tp.size= 1;
  37. return tot;
  38. }
  39. inline int copy_node(int p)
  40. {
  41. int ret= new_node();
  42. tree[ret]=tree[p];
  43. return ret;
  44. }
  45. inline void push_up(int p)
  46. {
  47. tree[p].size= tls(p).size+ trs(p).size+ 1;
  48. tree[p].sum= tls(p).sum+ trs(p).sum+ t(p).val;
  49. }
  50. inline void push_down(int p)
  51. {
  52. if(! t(p).tag) return;
  53. if( ls(p)) ls(p)= copy_node( ls(p));
  54. if( rs(p)) rs(p)= copy_node( rs(p));
  55. swap( ls(p), rs(p));
  56. if( ls(p)) tls(p).tag^= 1;
  57. if( rs(p)) trs(p).tag^= 1;
  58. tree[p].tag= 0;
  59. }
  60. void split(int p,int k,int &x,int &y)
  61. {
  62. if(!p){x=y= 0; return;}
  63. push_down(p);
  64. if( tls(p).size<k){x= copy_node(p); split( rs(x),k- tls(p).size -1, rs(x),y); push_up(x);}
  65. else{y= copy_node(p); split( ls(y),k,x, ls(y)); push_up(y);}
  66. }
  67. int merge(int x,int y)
  68. {
  69. if(!x||!y) return x|y;
  70. push_down(x); push_down(y);
  71. if( t(x).rand< t(y).rand){ rs(x)= merge( rs(x),y); push_up(x); return x;}
  72. else{ ls(y)= merge(x, ls(y)); push_up(y); return y;}
  73. }
  74. int main()
  75. {
  76. srand( 224144); scanf( "%d",&n);
  77. int cnt(0); int v,op;ll a,b; int x,y,z;
  78. while(n--)
  79. {
  80. scanf( "%d%d",&v,&op);
  81. if(op== 1)
  82. {
  83. scanf( "%lld%lld",&a,&b);
  84. a^=lastans;b^=lastans;
  85. split(rt[v],a,x,y);
  86. rt[++cnt]= merge( merge(x, new_node(b)),y);
  87. }
  88. if(op== 2)
  89. {
  90. scanf( "%lld",&a);
  91. a^=lastans;
  92. split(rt[v],a,x,z);
  93. split(x,a -1,x,y);
  94. rt[++cnt]= merge(x,z);
  95. }
  96. if(op== 3)
  97. {
  98. scanf( "%lld%lld",&a,&b);
  99. a^=lastans;b^=lastans;
  100. split(rt[v],b,x,z);
  101. split(x,a -1,x,y);
  102. t(y).tag^= 1;
  103. rt[++cnt]= merge( merge(x,y),z);
  104. }
  105. if(op== 4)
  106. {
  107. scanf( "%lld%lld",&a,&b);
  108. a^=lastans;b^=lastans;
  109. split(rt[v],b,x,z);
  110. split(x,a -1,x,y);
  111. printf( "%lld\n",lastans= t(y).sum);
  112. rt[++cnt]= merge( merge(x,y),z);
  113. }
  114. }
  115. return 0;
  116. }

C - 可持久化平衡树

主要思路:FHQ Treap + 可持久化

普通FHQ Treap加上一点可持久化的东西如下:(打上注释的代码是可持久化的特殊操作)


  
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstdlib>
  4. #include<cstring>
  5. #include<cmath>
  6. #include<algorithm>
  7. #include<string>
  8. #include<vector>
  9. #include<set>
  10. #include<queue>
  11. #include<stack>
  12. using namespace std;
  13. #define go(i,j,n,k) for(int i=j;i<=n;i+=k)
  14. #define fo(i,j,n,k) for(int i=j;i>=n;i-=k)
  15. #define rep(i,x) for(int i=h[x];i;i=e[i].nxt)
  16. #define mn 500010
  17. #define ld long double
  18. #define fi first
  19. #define se second
  20. #define inf 1<<30
  21. #define ll long long
  22. #define root 1,n,1
  23. #define lson l,m,rt<<1
  24. #define rson m+1,r,rt<<1|1
  25. #define bson l,r,rt
  26. inline ll read(){
  27. ll x= 0,f= 1; char ch= getchar();
  28. while(ch> '9'||ch< '0'){ if(ch== '-')f=-f;ch= getchar();}
  29. while(ch>= '0'&&ch<= '9'){x=x* 10+ch- '0';ch= getchar();}
  30. return x*f;
  31. }
  32. struct edge{
  33. int ch[ 2], sze, pri;
  34. ll w;
  35. } z[mn * 50];
  36. int rot[mn], xx, yy, zz, n, cnt;
  37. inline void update(int rt) {
  38. z[rt].sze = 1;
  39. if(z[rt].ch[ 0]) z[rt].sze += z[z[rt].ch[ 0]].sze;
  40. if(z[rt].ch[ 1]) z[rt].sze += z[z[rt].ch[ 1]].sze;
  41. }
  42. inline int newnode(ll w = 0) {
  43. z[++cnt].w = w;
  44. z[cnt].sze = 1;
  45. z[cnt].pri = rand();
  46. return cnt;
  47. }
  48. inline int merge(int x, int y) {
  49. if(!x || !y) return x + y;
  50. if(z[x].pri < z[y].pri) {
  51. int rt = newnode();
  52. z[rt] = z[x];
  53. z[rt].ch[ 1] = merge(z[rt].ch[ 1], y);
  54. update(rt);
  55. return rt;
  56. } else {
  57. int rt = newnode();
  58. z[rt] = z[y];
  59. z[rt].ch[ 0] = merge(x, z[rt].ch[ 0]);
  60. update(rt);
  61. return rt;
  62. }
  63. }
  64. inline void split(int rt, ll k, int &x, int &y) {
  65. if(!rt) x = y = 0;
  66. else {
  67. if(z[rt].w <= k) {
  68. x = newnode();
  69. z[x] = z[rt];
  70. split(z[x].ch[ 1], k, z[x].ch[ 1], y);
  71. update(x);
  72. } else {
  73. y = newnode();
  74. z[y] = z[rt];
  75. split(z[y].ch[ 0], k, x, z[y].ch[ 0]);
  76. update(y);
  77. }
  78. }
  79. }
  80. inline int findkth(int rt, int k) {
  81. while( 1119) {
  82. if(k <= z[z[rt].ch[ 0]].sze)
  83. rt = z[rt].ch[ 0];
  84. else {
  85. if(z[rt].ch[ 0]) k -= z[z[rt].ch[ 0]].sze;
  86. if(!--k) return rt;
  87. rt = z[rt].ch[ 1];
  88. }
  89. }
  90. }
  91. int main(){
  92. n = read();
  93. go(i, 1, n, 1) {
  94. xx = yy = zz = 0;
  95. int tmp = read(), s = read(); ll a = read();
  96. rot[i] = rot[tmp];
  97. if(s == 1) {
  98. split(rot[i], a, xx, yy);
  99. rot[i] = merge( merge(xx, newnode(a)), yy);
  100. } else if(s == 2) {
  101. split(rot[i], a, xx, zz);
  102. split(xx, a - 1, xx, yy);
  103. yy = merge(z[yy].ch[ 0], z[yy].ch[ 1]);
  104. rot[i] = merge( merge(xx, yy), zz);
  105. } else if(s == 3) {
  106. split(rot[i], a - 1, xx, yy);
  107. printf( "%lld\n", z[xx].sze + 1);
  108. rot[i] = merge(xx, yy);
  109. } else if(s == 4) {
  110. printf( "%lld\n", z[ findkth(rot[i], a)].w);
  111. } else if(s == 5) {
  112. split(rot[i], a - 1, xx, yy);
  113. if(xx == 0) {
  114. printf( "-2147483647\n");
  115. continue;
  116. }
  117. printf( "%lld\n", z[ findkth(xx, z[xx].sze)].w);
  118. rot[i] = merge(xx, yy);
  119. } else if(s == 6) {
  120. split(rot[i], a, xx, yy);
  121. if(yy == 0) {
  122. printf( "2147483647\n");
  123. continue;
  124. }
  125. printf( "%lld\n", z[ findkth(yy, 1)].w);
  126. rot[i] = merge(xx, yy);
  127. }
  128. }
  129. return 0;
  130. }

D - 维护数列

首先,要有一点splay维护区间操作的基础。

splay维护区间的基本原理,就是将区间[l,r]的端点l-1,和r+1不断的通过伸展操作即splay到根,将l-1伸展到根,将r+1伸展到根的右儿子,那么[l,r]这段区间就在根的右儿子的左儿子上了。

特别要注意的是,==这里的l,r不是给出的区间端点的编号,而是我们在平衡树的中序遍历中区间端点的编号。即在平衡树中排名(rank)为l,r的两个节点的真实编号,而对于l=1或r=n的情况就非常特殊了,我们有两种解决方案,一种就是分类讨论,将这4种情况枚举,然后进行操作,这么做固然可行,但是当操作变多时,会使整个程序显得繁琐,并且难于调试。另一种解决方案就是建立虚拟节点,我们把需要维护的区间全部变成[l+1,r+1],那么我们虚拟出一个1号节点和一个n+2号节点,那么整个操作就显得十分自然了==。

那么问题就明显是一个splay的基本模板题了。而维护区间翻转,在洛谷的P3391文艺平衡树中有更裸的题目。

这里,一个操作一个操作的解决。

初始化

首先,对于原序列,我们不应该一个一个读入,然后插入,那么效率就是O(nlogn),而splay的常数本身就很大,所以考虑一个优化,就是把原序列一次性读入后,直接类似线段树的build,搞一个整体建树,即不断的将当前点维护的区间进行二分,到达单元素区间后,就把对应的序列值插入进去,这样,我们一开始建的树就是一个非常平衡的树,可以使后续操作的常数更小,并且建树整个复杂度只是O(2n)的。

Insert操作

其次,我们来考虑一下如何维护一个insert操作。我们可以这么做,首先如上将需要insert的区间变成节点数目为tot的平衡树,然后把k+1(注意我们将需要操作的区间右移了一个单位,所以题目所给k就是我们需要操作的k+1)移到根节点的位置,把原树中的k+2移到根节点的右儿子的位置。然后把需要insert的区间,先build成一个平衡树,把需要insert的树的根直接挂到原树中k+1的左儿子上就行了。

Delete操作

再然后,我们来考虑一下delete操作,我们同样的,把需要delete的区间变成[k+1,k+tot](注意,是删去k后面的tot个数,那么可以发现我们需要操作的原区间是[k,k+tot-1]!),然后把k号节点移到根节点的位置,把k+tot+2移到根节点的右儿子位置,然后直接把k+tot+2的左儿子的指针清为0,就把这段区间删掉了。可以发现,比insert还简单一点。

Reverse操作

接下来,这道题的重头戏就要开始了。splay的区间操作基本原理还类似于线段树的区间操作,即延迟修改,又称打懒标记。

对于翻转(reverse)操作,我们依旧是将操作区间变成[k+1,k+tot],然后把k和k+tot+1分别移到对应根的右儿子的位置,然后对这个右儿子的左儿子打上翻转标记即可。

Make-Same操作

对于Make-Same操作,我们同样需要先将需要操作的区间变成[k+1,k+tot],然后把k和k+tot+1分别移到根和右儿子的位置,然后对这个右儿子的左儿子打上修改标记即可。

Get-Sum操作

对于Get-Sum操作,我们还是将操作区间变成[k+1,k+tot],然后把k和k+tot+1分别移到根和右儿子的位置,然后直接输出这个右儿子的左儿子上的sum记录的和。

Max-Sum操作

对于这个求最大子序列的操作,即Max-Sum操作,我们不能局限于最开始学最大子序列的线性dp方法,而是要注意刚开始,基本很多书都会介绍一个分治的O(nlogn)的方法,但是由于存在O(n)的方法,导致这个方法并不受重视,但是这个方法确实很巧妙,当数列存在修改操作时,线性的算法就不再适用了。

这种带修改的最大子序列的问题,最开始是由线段树来维护,具体来说就是,对于线段树上的每个节点所代表的区间,维护3个量:lx表示从区间左端点l开始的连续的前缀最大子序列。rx表示从区间右端点r开始的连续的后缀最大子序列。mx表示这个区间中的最大子序列。

那么在合并[l,mid]和[mid+1,r]时,就类似一个dp的过程了!

懒标记的处理

最后,相信认真看了的童鞋会有疑问,这个标记怎么下传呢?首先,我们在每次将k和k+tot+1移到对应的位置时,需要一个类似查找k大值的find操作,即找出在平衡树中,实际编号为k在树中中序遍历的编号,这个才是我们真正需要处理的区间端点编号,那么就好了,我们只需在查找的过程中下传标记就好了!(其实线段树中也是这么做的),因为我们所有的操作都需要先find一下,所以我们可以保证才每次操作的结果计算出来时,对应的节点的标记都已经传好了。而我们在修改时,直接修改对应节点的记录标记和懒标记,因为我们的懒标记记录的都是已经对当前节点产生贡献,但是还没有当前节点的子树区间产生贡献!然后就是每处有修改的地方都要pushup一下就好了。


  
  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<cstring>
  4. #include<queue>
  5. #define rint register int
  6. #define For(i,a,b) for (rint i=a;i<=b;++i)
  7. using namespace std;
  8. const int inf= 0x3f3f3f3f;
  9. const int N= 1e6+ 17;
  10. int n,m,rt,cnt;
  11. int a[N],id[N],fa[N],c[N][ 2];
  12. int sum[N],sz[N],v[N],mx[N],lx[N],rx[N];
  13. bool tag[N],rev[N];
  14. //tag表示是否有统一修改的标记,rev表示是否有统一翻转的标记
  15. //sum表示这个点的子树中的权值和,v表示这个点的权值
  16. queue< int> q;
  17. inline int read(){
  18. rint x= 0,f= 1; char ch= getchar();
  19. while (ch< '0' || ch> '9'){ if (ch== '-')f= -1;ch= getchar();}
  20. while ( '0'<=ch && ch<= '9')x=(x<< 1)+(x<< 3)+(ch^ 48),ch= getchar();
  21. return x*f;
  22. }
  23. inline void pushup(rint x){
  24. rint l=c[x][ 0],r=c[x][ 1];
  25. sum[x]=sum[l]+sum[r]+v[x];
  26. sz[x]=sz[l]+sz[r]+ 1;
  27. mx[x]= max(mx[l], max(mx[r],rx[l]+v[x]+lx[r]));
  28. lx[x]= max(lx[l],sum[l]+v[x]+lx[r]);
  29. rx[x]= max(rx[r],sum[r]+v[x]+rx[l]);
  30. }
  31. //上传记录标记
  32. inline void pushdown(rint x){
  33. rint l=c[x][ 0],r=c[x][ 1];
  34. if (tag[x]){
  35. rev[x]=tag[x]= 0; //我们有了一个统一修改的标记,再翻转就没有什么意义了
  36. if (l)tag[l]= 1,v[l]=v[x],sum[l]=v[x]*sz[l];
  37. if (r)tag[r]= 1,v[r]=v[x],sum[r]=v[x]*sz[r];
  38. if (v[x]>= 0){
  39. if (l)lx[l]=rx[l]=mx[l]=sum[l];
  40. if (r)lx[r]=rx[r]=mx[r]=sum[r];
  41. } else{
  42. if (l)lx[l]=rx[l]= 0,mx[l]=v[x];
  43. if (r)lx[r]=rx[r]= 0,mx[r]=v[x];
  44. }
  45. }
  46. if (rev[x]){
  47. rev[x]= 0;rev[l]^= 1;rev[r]^= 1;
  48. swap(lx[l],rx[l]); swap(lx[r],rx[r]);
  49. //注意,在翻转操作中,前后缀的最长上升子序列都反过来了,很容易错
  50. swap(c[l][ 0],c[l][ 1]); swap(c[r][ 0],c[r][ 1]);
  51. }
  52. }
  53. //下传标记
  54. inline void rotate(rint x,rint &k){
  55. rint y=fa[x],z=fa[y],l=(c[y][ 1]==x),r=l^ 1;
  56. if (y==k)k=x; else c[z][c[z][ 1]==y]=x;
  57. fa[c[x][r]]=y;fa[y]=x;fa[x]=z;
  58. c[y][l]=c[x][r];c[x][r]=y;
  59. pushup(y); pushup(x);
  60. //旋转操作,一定要上传记录标记
  61. }
  62. inline void splay(rint x,rint &k){
  63. while (x!=k){
  64. int y=fa[x],z=fa[y];
  65. if (y!=k){
  66. if (c[z][ 0]==y ^ c[y][ 0]==x) rotate(x,k);
  67. else rotate(y,k);
  68. }
  69. rotate(x,k);
  70. }
  71. }
  72. //这是整个程序的核心之一,毕竟是伸展操作嘛
  73. inline int find(rint x,rint rk){
  74. pushdown(x);
  75. //因为所有的操作都需要find,所以我们只需在这里下传标记就行了
  76. rint l=c[x][ 0],r=c[x][ 1];
  77. if (sz[l]+ 1==rk) return x;
  78. if (sz[l]>=rk) return find(l,rk);
  79. else return find(r,rk-sz[l] -1);
  80. }
  81. //这个find是我们整个程序的核心之二
  82. //因为我们的区间翻转和插入及删除的操作的存在
  83. //我们维护的区间的实际编号并不是连续的
  84. //而,我们需要操作的区间又对应着平衡树的中序遍历中的那段区间
  85. //所以这个find很重要
  86. inline void recycle(rint x){
  87. rint &l=c[x][ 0],&r=c[x][ 1];
  88. if (l) recycle(l);
  89. if (r) recycle(r);
  90. q. push(x);
  91. fa[x]=l=r=tag[x]=rev[x]= 0;
  92. }
  93. //这就是用时间换空间的回收冗余编号机制,很好理解
  94. inline int split(rint k,rint tot){
  95. rint x= find(rt,k),y= find(rt,k+tot+ 1);
  96. splay(x,rt); splay(y,c[x][ 1]);
  97. return c[y][ 0];
  98. }
  99. //这个split操作是整个程序的核心之三
  100. //我们通过这个split操作,找到[k+1,k+tot],并把k,和k+tot+1移到根和右儿子的位置
  101. //然后我们返回了这个右儿子的左儿子,这就是我们需要操作的区间
  102. inline void query(rint k,rint tot){
  103. rint x= split(k,tot);
  104. printf( "%d\n",sum[x]);
  105. }
  106. inline void modify(rint k,rint tot,rint val){
  107. rint x= split(k,tot),y=fa[x];
  108. v[x]=val;tag[x]= 1;sum[x]=sz[x]*val;
  109. if (val>= 0)lx[x]=rx[x]=mx[x]=sum[x];
  110. else lx[x]=rx[x]= 0,mx[x]=val;
  111. pushup(y); pushup(fa[y]);
  112. //每一步的修改操作,由于父子关系发生改变
  113. //及记录标记发生改变,我们需要及时上传记录标记
  114. }
  115. inline void rever(rint k,rint tot){
  116. rint x= split(k,tot),y=fa[x];
  117. if (!tag[x]){
  118. rev[x]^= 1;
  119. swap(c[x][ 0],c[x][ 1]);
  120. swap(lx[x],rx[x]);
  121. pushup(y); pushup(fa[y]);
  122. }
  123. //同上
  124. }
  125. inline void erase(rint k,rint tot){
  126. rint x= split(k,tot),y=fa[x];
  127. recycle(x);c[y][ 0]= 0;
  128. pushup(y); pushup(fa[y]);
  129. //同上
  130. }
  131. inline void build(rint l,rint r,rint f){
  132. rint mid=(l+r)>> 1,now=id[mid],pre=id[f];
  133. if (l==r){
  134. mx[now]=sum[now]=a[l];
  135. tag[now]=rev[now]= 0;
  136. //这里这个tag和rev的清0是必要,因为这个编号可能是之前冗余了
  137. lx[now]=rx[now]= max(a[l], 0);
  138. sz[now]= 1;
  139. }
  140. if (l<mid) build(l,mid -1,mid);
  141. if (mid<r) build(mid+ 1,r,mid);
  142. v[now]=a[mid]; fa[now]=pre;
  143. pushup(now);
  144. //上传记录标记
  145. c[pre][mid>=f]=now;
  146. //当mid>=f时,now是插入到又区间取了,所以c[pre][1]=now,当mid<f时同理
  147. }
  148. inline void insert(rint k,rint tot){
  149. For(i, 1,tot)a[i]= read();
  150. For(i, 1,tot)
  151. if (!q. empty())id[i]=q. front(),q. pop();
  152. else id[i]=++cnt; //利用队列中记录的冗余节点编号
  153. build( 1,tot, 0); //将读入的tot个树建成一个平衡树
  154. rint z=id[( 1+tot)>> 1]; //取中点为根
  155. rint x= find(rt,k+ 1),y= find(rt,k+ 2);
  156. //首先,依据中序遍历,找到我们需要操作的区间的实际编号
  157. splay(x,rt); splay(y,c[x][ 1]);
  158. //把k+1(注意我们已经右移了一个单位)和(k+1)+1移到根和右儿子
  159. fa[z]=y;c[y][ 0]=z;
  160. //直接把需要插入的这个平衡树挂到右儿子的左儿子上去就好了
  161. pushup(y); pushup(x);
  162. //上传记录标记
  163. }
  164. //对于具体在哪里上传标记和下传标记
  165. //可以这么记,只要用了split就要重新上传标记
  166. //只有find中需要下传标记
  167. //但其实,你多传几次是没有关系的,但是少传了就不行了
  168. int main(){
  169. n= read(),m= read();
  170. mx[ 0]=a[ 1]=a[n+ 2]=-inf;
  171. For(i, 1,n)a[i+ 1]= read();
  172. For(i, 1,n+ 2)id[i]=i; //虚拟了两个节点1和n+2,然后把需要操作区间整体右移一个单位
  173. build( 1,n+ 2, 0); //建树
  174. rt=(n+ 3)>> 1;cnt=n+ 2; //取最中间的为根
  175. rint k,tot,val; char ch[ 10];
  176. while (m--){
  177. scanf( "%s",ch);
  178. if (ch[ 0]!= 'M' || ch[ 2]!= 'X') k= read(),tot= read();
  179. if (ch[ 0]== 'I') insert(k,tot);
  180. if (ch[ 0]== 'D') erase(k,tot);
  181. if (ch[ 0]== 'M'){
  182. if (ch[ 2]== 'X') printf( "%d\n",mx[rt]);
  183. else val= read(), modify(k,tot,val);
  184. }
  185. if (ch[ 0]== 'R') rever(k,tot);
  186. if (ch[ 0]== 'G') query(k,tot);
  187. }
  188. return 0;
  189. }

E - 文本编辑器

平衡树的模板题。这里使用的是非旋Treap。由于这道题用平衡树维护的是文本,所以只需要考虑文字之间的相对顺序,因此之后所有对于树的split操作指的都是按照排名进行split。
        不难想到用一个变量p存下来光标前面一个字符的下标(下标从11开始)。下面考虑对于题目中各个操作的处理:
        1.Move,Prev,Next:对于这三个操作,修改p即可。
        2.Delete:设文本为S。把树分成三个部分,一个为x树,对应[1,p]的文本;一个为y树,对应要删除的(p,p+n]的文本;还有一个为z树,对应(p+n,∣S∣]的文本。于是乎y树就是要删除的部分。我们直接将x树和z树合成新树即可。
        3.Rotate:同上,把需要修改的部分提出来,打标记。然后在拆分、修改的操作的时候下传标记即可。
        4.Get:相当于求文本中第p+1小的值。套板子。
        5.Insert:先把书分成两个部分,一个为x树,对应[1,p];另一个为z树,对应(p,∣S∣]。下面有两种处理方法。一种是把要插入的串暴力一个一个字符地插入到x树,然后合并x和z,时间是可以过的,但是显然效率很低;另一种是对于要插入的串建立一颗y树,再把x,y,z合并起来。因为我很懒, 我用的第一种方法。
        由于出题人很恶心,插入的时候有可能出现换行符也需要插入,所以需要严格地按照输入的n来插入而不是用scanf之类的。同时,如果Get需要输出换行符,就只需要输出一个。
        然后你就发现这道黑题就被这样愉快地水过去解决了。


  
  1. #include <cstdio>
  2. #include <cstdlib>
  3. #include <cstring>
  4. #define random myRandom
  5. const int MAXSIZ = 1024 * 1024 * 2 + 5;
  6. template<typename _T>
  7. void read ( _T &x )
  8. {
  9. x = 0; char s = getchar(); int f = 1;
  10. while( s > '9' || s < '0' ){ if( s == '-' ) f = -1; s = getchar();}
  11. while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
  12. x *= f;
  13. }
  14. template<typename _T>
  15. void write ( _T x )
  16. {
  17. if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
  18. if( 9 < x ){ write( x / 10 ); }
  19. putchar( x % 10 + '0' );
  20. }
  21. template<typename _T>
  22. void swapp ( _T &x, _T &y ) { _T t = x; x = y, y = t; }
  23. int ch[MAXSIZ][ 2], aux[MAXSIZ], siz[MAXSIZ];
  24. char val[MAXSIZ], S[MAXSIZ];
  25. bool rot[MAXSIZ];
  26. int nsiz, mpos = 0, rt;
  27. void srd() { int a, *aa = &a; srand( ( unsigned long long ) aa ); }
  28. int random() { return rand() * rand(); }
  29. int newNode( const char c ) { aux[++ nsiz] = random(), siz[nsiz] = 1, val[nsiz] = c, rot[nsiz] = false; return nsiz; }
  30. void upt( const int u ) { siz[u] = siz[ch[u][ 0]] + siz[ch[u][ 1]] + 1; }
  31. void swp( const int u ) { swapp( ch[u][ 0], ch[u][ 1] ), rot[u] ^= 1; }
  32. void normalize( const int u )
  33. {
  34. if( ! rot[u] ) return ;
  35. swp( ch[u][ 0] ), swp( ch[u][ 1] );
  36. rot[u] = false;
  37. }
  38. void splitRnk( const int u, const int k, int &x, int &y )
  39. {
  40. if( ! u ) { x = y = 0; return ; }
  41. normalize( u );
  42. if( k <= siz[ch[u][ 0]] ) y = u, splitRnk( ch[u][ 0], k, x, ch[u][ 0] );
  43. else x = u, splitRnk( ch[u][ 1], k - siz[ch[u][ 0]] - 1, ch[u][ 1], y );
  44. upt( u );
  45. }
  46. int merg( const int u, const int v )
  47. {
  48. if( ! u || ! v ) return u + v;
  49. if( aux[u] < aux[v] ) { normalize( u ), ch[u][ 1] = merg( ch[u][ 1], v ), upt( u ); return u; }
  50. else { normalize( v ), ch[v][ 0] = merg( u, ch[v][ 0] ), upt( v ); return v; }
  51. }
  52. void insert( const char *buf )
  53. {
  54. int l = strlen( buf ), y;
  55. splitRnk( rt, mpos, rt, y );
  56. for( int i = 0 ; i < l ; i ++ ) rt = merg( rt, newNode( buf[i] ) );
  57. rt = merg( rt, y );
  58. }
  59. void del( const int length )
  60. {
  61. int x, y;
  62. splitRnk( rt, mpos, rt, x ),
  63. splitRnk( x, length, x, y );
  64. rt = merg( rt, y );
  65. }
  66. void rotate( const int length )
  67. {
  68. int x, y;
  69. splitRnk( rt, mpos, rt, x ), splitRnk( x, length, x, y );
  70. swp( x ), rt = merg( merg( rt, x ), y );
  71. }
  72. char Get()
  73. {
  74. int u = rt, k = mpos + 1;
  75. while( true )
  76. {
  77. normalize( u );
  78. if( k <= siz[ch[u][ 0]] ) u = ch[u][ 0];
  79. else if( k <= siz[ch[u][ 0]] + 1 ) return val[u];
  80. else k -= siz[ch[u][ 0]] + 1, u = ch[u][ 1];
  81. }
  82. }
  83. int main()
  84. {
  85. srd();
  86. char op[ 10];
  87. int N, k;
  88. read( N );
  89. while( N -- )
  90. {
  91. scanf( "%s", op );
  92. if( op[ 0] == 'M' ) { read( mpos ); }
  93. if( op[ 0] == 'I' )
  94. {
  95. read( k );
  96. for( int i = 0 ; i < k ; i ++ ) S[i] = getchar();
  97. insert( S );
  98. for( int i = 0 ; i < k ; i ++ ) S[i] = '\0';
  99. }
  100. if( op[ 0] == 'D' ) read( k ),
  101. del( k );
  102. if( op[ 0] == 'R' ) read( k ), rotate( k );
  103. if( op[ 0] == 'G' )
  104. {
  105. char tmp; putchar( tmp = Get() );
  106. if( tmp ^ '\n' ) putchar( '\n' );
  107. }
  108. if( op[ 0] == 'P' ) mpos --;
  109. if( op[ 0] == 'N' ) mpos ++;
  110. }
  111. return 0;
  112. }


转载:https://blog.csdn.net/m0_61735576/article/details/128720370
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场