[uoj173]鏖战表达式
2018年论文题,上接loj2506,主要是论文中的第4章,也可快速跳至原题解
5.平衡树的嵌套问题
平衡树嵌套
所谓平衡树嵌套,就是若干棵平衡树,其中若干棵平衡树的根会指向另一颗平衡树上的一个节点
定义一棵平衡树的$W$为其子树内所有节点的$w_{x}$之和,再定义$w_{x}$为上述指向其的根的平衡树的$W$之和加1
(这两者定义并不嵌套,显然可以按层去计算$w_{x}$和$W$)
如果在一个平衡树中访问节点$x$的复杂度为$o(\log\frac{W}{w_{x}})$,那么就有以下结论——
定理9:以此法从全局的根访问一个节点$x$,复杂度为$o(k+\log n)$(其中$k$为从$x$到全局的根路径上所经过的平衡树数量,也即层数)
证明:从下往上依次记录在每一棵平衡树中的复杂度,即$\sum_{i=1}^{k}\log\frac{W_{i}}{w_{i}}+1$
根据定义,有$w_{i}\ge W_{i-1}$,不妨将其放缩,即$\sum_{i=1}^{k}\log\frac{W_{i}}{W_{i-1}}+1\sim o(k+\log n)$
而如果没有上述性质,即每一次都是$o(\log n)$的,那么总复杂度也就是$o(k\log n)$
下面,我们将要分别考虑Treap和Splay如何做到$o(\log\frac{W}{w_{x}})$的复杂度
Treap
考虑一个简单的构造,令节点$x$的随机值为$w_{x}$个随机值中的最大值
**定理10:**以此法随机并维护,节点$x$的期望深度为$o(\log\frac{W}{w_{x}})$
**证明:**为了方便表示,将节点按元素从小到大依次记为$a_{1},a_{2},...,a_{n}$,权值依次为$w_{1},w_{2},...,w_{n}$
类似前面,节点$a_{x}$的期望深度也就是有所有节点是其祖先的概率之和,即
$$
\sum_{i=1}^{x-1}\frac{w_{i}}{\sum_{j=i}^{x}w_{j}}+\sum_{i=x+1}^{n}\frac{w_{i}}{\sum_{j=x}^{i}w_{j}}
$$
(关于这里的概率,实际上也就是$\sum_{j=i}^{x}w_{j}$个随机值中最大值在$i$随机的$w_{i}$个数中,即此式)
将其展开,并进行放缩,即
$$
\le \sum_{i=1}^{x-1}\sum_{k=1}^{w_{i}}\frac{1}{\sum_{j=i+1}^{x}w_{j}+k}+\sum_{i=x+1}^{n}\sum_{k=1}^{w_{i}}\frac{1}{\sum_{j=x}^{i-1}w_{j}+k}
$$
每一次也就是一个区间,且这些区间恰好是连续的,即
$$
\sum_{i=w_{x}+1}^{\sum_{j=1}^{x}w_{j}}\frac{1}{i}+\sum_{i=w_{x}+1}^{\sum_{j=x}^{n}w_{j}}\frac{1}{i}\sim o(\log \frac{W}{w_{x}})
$$
当然这样随机的复杂度为$o(W)$,在某些情况下可能无法接受
一个简单的做法是记录子树的$w$之和,并以此为概率进行比较
另外还有一个比较有趣的做法,来实现这个随机——
对于一个随机变量$X$,令$P(x)$表示随机变量$X\le x$的概率
再构造一个函数$f(x)$,在其定义域中有$P(x)$的概率为函数值小于等于$x$,那么$f(x)$也就是一个随机$X$的函数,即只要在其定义域中等概率随机一个$x$,取$X=f(x)$即可
事实上,取$f(x)=P^{-1}(x)$即可,代入本题中,即$P(x)=x^{w}$,那么$f(x)=x^{\frac{1}{w}}$
考虑$x^{\frac{1}{w_{1}}}<y^{\frac{1}{w_{2}}}$的概率,也就是$x^{w2}<y^{w1}$
Splay
定理11:对节点$x$进行Splay操作的均摊复杂度为$o(\log\frac{W}{w_{x}})$
证明:令每一个节点的$sz_{x}$为子树内所有节点的权值之和,类似于结论2的证明,可以得到该结论
6.平衡树嵌套的应用
树链剖分
用平衡树来维护一条重链,对于顶端节点不是根节点的重链,将其连向顶端节点的父亲
根据轻边的性质,不难证明$k\sim o(\log n)$,因此单次复杂度仅为$o(\log n)$,事实上大部分使用线段树维护树链剖分都可以通过这样的技巧优化来减少一个$\log$
LCT
定理12:LCT的单次操作时间复杂度为均摊$o(\log n)$
证明:对于$o(k+\log n)$的复杂度,后者显然可以忽略, 对前者$k$进行势能分析:
(以下描述中,轻重儿子是根据子树大小确定,实虚儿子是根据LCT确定)
定义当前这个结构的势能为其中实儿子与重儿子不同的节点数(重儿子任取),考虑将一个节点到根路径上所有虚边都变为实边,对这个节点分类讨论:
1.若其是轻儿子,显然这样的节点数不多于$\log n$个,且每一个节点最多导致1次操作以及1个势能,即单个节点是$o(1)$的,那么总共即$o(\log n)$的
2.若其是重儿子,那么将实儿子修改为其,会导致势能减小1,即本次操作无贡献
且由于势能大小为不超过$n$的非负整数,因此即在最外面还会有一个$o(n)$,也可以接受
(另外,这也就说明了LCT上访问一个节点必须access,而不能直接去访问)
关于Treap维护LCT
看似Treap也可以维护LCT,但实际上并不能实现
这是因为在维护过程中,我们需要修改一个节点的$w_{x}$,在Splay中我们只需要将其Splay至根即可,但在Treap中我们要给其新的随机值并进行调整
首先,直接暴力删除再插入,也可以做到$o(\log n)$,那么也就是单次$o(k\log n)$,对$k$进行一样的均摊分析,也就得到了Treap维护LCT单次操作是$o(\log^{2}n)$的
当然,我们也可以略微优化这个调整(但似乎并不能该边其维护LCT的复杂度),若其随机值增加即将其下旋(将随机值较小的旋上来),否则将其上旋
那么这样的复杂度也就是其与最终状态的期望深度差,有以下结论——
定理13:假设其权值由$w$变为$w'$,则其期望深度差为$\log\frac{\max(w,w')}{\min(w,w')}$
证明:与之前一样,假设权值依次为$w_{1},w_{2},...,w_{n}$,且其中$w_{x}$的权值增加了$\Delta$($\Delta\ge 0$),我们要证明的也就是深度差是$o(\log \frac{w_{x}+\Delta}{w_{x}})$
深度也用每一个点的概率来表示,即
$$
\sum_{i=1}^{x-1}\frac{w_{i}}{\sum_{j=i}^{x}w_{j}}-\frac{w_{i}}{\sum_{j=i}^{x}w_{j}+\Delta}+\sum_{i=x+1}^{n}\frac{w_{i}}{\sum_{j=x}^{i}w_{j}}-\frac{w_{i}}{\sum_{j=x}^{i}w_{j}+\Delta}
$$
注意到$f(x)=\frac{1}{x}-\frac{1}{x+\Delta}$在$x>0$时是递减的,那么就可以与之前一样,将前者表示为
$$
\frac{w_{i}}{\sum_{j=i}^{x}w_{j}}-\frac{w_{i}}{\sum_{j=i}^{x}w_{j}+\Delta}=w_{i}\times f(\sum_{j=i}^{x}w_{j})\le \sum_{k=1}^{w_{i}}f(\sum_{j=i+1}^{x}w_{j}+k)=\sum_{i=w_{x}+1}^{\sum_{j=1}^{x}w_{j}}f(i)
$$
将$f(x)=\frac{1}{x}-\frac{1}{x+\Delta}$代入后分别计算,即$o(\log\frac{S}{w_{x}}-\log\frac{S}{w_{x}+\Delta})=o(\log\frac{w_{x}+\Delta}{w_{x}})$(其中$S=\sum_{j=1}^{x}w_{j}$)
后半部分类似,也可以得到该结论,另外将$\Delta<0$看作反过来即可
这个结论虽然并不能让Treap维护LCT的理论复杂度更优,但可能会有用处
将表达式其看作一个$2n-1$的序列,建立一棵Treap使得其中序遍历即该序列,并且保证父亲节点运算优先级不大于儿子(其中元素看作$k+1$级的操作),那么不难证明此时不断合并即可得到结果
进一步的,考虑对于与这棵Treap根节点相连的连通块,必然是优先级最低的所有操作,且之后剩下的若干个也同样满足此性质Treap,这也就构成了嵌套的关系,根据定理9每一个节点深度是$o(k+\log n)$的
(一个节点子树内所有节点的$w$之和,不难发现也就是其Treap的子树大小,以此为概率进行比较)
综上,使用合并和分裂维护,复杂度都是深度,即$o(k+\log n)$,可以通过
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include "expr.h" 2 #include<bits/stdc++.h> 3 using namespace std; 4 #define N 20005 5 #define s(p) f[k].ch[p] 6 struct node{ 7 int op,sz,tag,ch[2]; 8 Data val; 9 bool operator < (const node &k)const{ 10 return (op<k.op)||(op==k.op)&&(rand()%(sz+k.sz)<sz); 11 } 12 }f[N*1000]; 13 int V,m,a,b,c,rt[N]; 14 int New(int k){ 15 f[++V]=f[k]; 16 return V; 17 } 18 void rev(int &k){ 19 k=New(k); 20 f[k].tag^=1; 21 swap(s(0),s(1)); 22 } 23 void up(int k){ 24 f[k].sz=f[s(0)].sz+f[s(1)].sz+1; 25 if ((s(0))&&(s(1)))f[k].val=F(f[s(0)].val,f[s(1)].val,f[k].op); 26 else{ 27 if (s(0))f[k].val=f[s(0)].val; 28 if (s(1))f[k].val=f[s(1)].val; 29 } 30 } 31 void down(int k){ 32 if (f[k].tag){ 33 if (s(0))rev(s(0)); 34 if (s(1))rev(s(1)); 35 f[k].tag=0; 36 } 37 } 38 int merge(int x,int y){ 39 if ((!x)||(!y))return x+y; 40 down(x),down(y); 41 int k; 42 if (f[x]<f[y]){ 43 k=New(x); 44 s(1)=merge(f[x].ch[1],y); 45 } 46 else{ 47 k=New(y); 48 s(0)=merge(x,f[y].ch[0]); 49 } 50 up(k); 51 return k; 52 } 53 void split(int k,int x,int &a,int &b){ 54 if (!k){ 55 a=b=0; 56 return; 57 } 58 down(k); 59 if (x<=f[s(0)].sz){ 60 b=New(k); 61 split(s(0),x,a,f[b].ch[0]); 62 up(b); 63 } 64 else{ 65 a=New(k); 66 split(s(1),x-f[s(0)].sz-1,f[a].ch[1],b); 67 up(a); 68 } 69 } 70 void init(int id,int n,int m,int k,const Data *a,const int *op){ 71 srand(time(0)); 72 V=2*n-1; 73 for(int i=1;i<2*n;i++){ 74 if (i%2==0)f[i].op=op[i/2]; 75 else{ 76 f[i].val=a[i/2]; 77 f[i].op=k+1; 78 } 79 f[i].sz=1; 80 rt[0]=merge(rt[0],i); 81 } 82 } 83 Data modify_data(int id,int x,Data y){ 84 x=x*2+1; 85 split(rt[id],x-1,a,b); 86 split(b,1,b,c); 87 b=New(b); 88 f[b].val=y; 89 rt[++m]=merge(merge(a,b),c); 90 return f[rt[m]].val; 91 } 92 Data modify_op(int id,int x,int y){ 93 x*=2; 94 split(rt[id],x-1,a,b); 95 split(b,1,b,c); 96 b=New(b); 97 f[b].op=y; 98 rt[++m]=merge(merge(a,b),c); 99 return f[rt[m]].val; 100 } 101 Data reverse(int id,int x,int y){ 102 x=x*2+1,y=y*2+1; 103 split(rt[id],x-1,a,b); 104 split(b,y-x+1,b,c); 105 rev(b); 106 rt[++m]=merge(merge(a,b),c); 107 return f[rt[m]].val; 108 }
(似乎被卡了操作次数)