重量平衡树
什么是重量平衡树?
在插入/删除操作之后,为了保持树的平衡而重构的子树大小为均摊/期望
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
比如:替罪羊树,treap。
动态下标
平衡树上判断两个点的大小是一个
log
\log
log 的。
怎么更快地做这件事呢?
给每个点一个标号,
d
f
s
dfs
dfs 序上靠前的标号小。设一个节点
x
x
x 对应的区间是
(
l
,
r
)
(l,r)
(l,r),令它的标号为
m
i
d
=
(
l
+
r
)
/
2
mid=(l+r)/2
mid=(l+r)/2。让它的左右儿子对应的区间为
(
l
,
m
i
d
)
(l,mid)
(l,mid) 和
(
m
i
d
,
r
)
(mid,r)
(mid,r),然后递归计算标号。
但是旋转操作会影响标号的分配。所以用重量平衡树做这个东西。
BZOJ3600 没有人的算术
如果是普通的数,用线段树即可。
问题在于区间合并的时候如何比较两个数大小。这就是动态下标平衡树的应用。平衡树里的每个点存两个信息,一个是这个点的权值,另一个是一个 pair,表示它是由哪两个点合成的(是点标号不是权值),pair 是用于插入平衡树的比较。为了维护 pair 内元素的有序性,我们的平衡树只插入不删除。
复杂度
O
(
q
log
n
)
O(q\log n)
O(qlogn)。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define ld long double
using namespace std;
const double INF=1<<30;
const int N=600010;
typedef pair <int,int> P;
struct node {
int x,y;
node(int _x=0,int _y=0) {x=_x,y=_y;}
}pp[N];
char s[10];
int a[N],b[N],ind[N],mx[N],size[N],son[N][2],tot,cnt,rt;
double val[N];
bool operator < (node a,node b) {return (a.x^b.x)?val[a.x]<val[b.x]:val[a.y]<val[b.y];}
bool operator == (node a,node b) {return a.x==b.x&&a.y==b.y;}
bool operator > (node a,node b) {return (a.x^b.x)?val[a.x]>val[b.x]:val[a.y]>val[b.y];}
int read()
{
int x=0;char c=getchar(),flag='+';
while(!isdigit(c)) flag=c,c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return flag=='-'?-x:x;
}
void up(int root)
{
if(val[ind[mx[root<<1]]]>=val[ind[mx[root<<1|1]]]) mx[root]=mx[root<<1];
else mx[root]=mx[root<<1|1];
}
void build(int root,int l,int r)
{
if(l==r)
{
mx[root]=l;
return;
}
int mid=l+r>>1;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
up(root);
}
void update(int root,int l,int r,int x)
{
if(l==r) return;
int mid=l+r>>1;
if(x<=mid) update(root<<1,l,mid,x);
else update(root<<1|1,mid+1,r,x);
up(root);
}
int query(int root,int l,int r,int x,int y)
{
if(x<=l&&y>=r) return mx[root];
int mid=l+r>>1;
if(y<=mid) return query(root<<1,l,mid,x,y);
if(x>mid) return query(root<<1|1,mid+1,r,x,y);
int ss=query(root<<1,l,mid,x,y),tt=query(root<<1|1,mid+1,r,x,y);
if(val[ind[ss]]>=val[ind[tt]]) return ss;
return tt;
}
void push(int x) {size[x]=size[son[x][0]]+size[son[x][1]]+1;}
bool bad(int x) {return max(size[son[x][0]],size[son[x][1]])>0.75*size[x];}
void pia(int root)
{
if(!root) return;
pia(son[root][0]);
b[++cnt]=root;
pia(son[root][1]);
son[root][0]=son[root][1]=0;
}
void build(int &root,int l,int r,double L,double R)
{
if(l>r) return;
int mid=l+r>>1;
root=b[mid];
val[root]=(L+R)/2;
build(son[root][0],l,mid-1,L,val[root]);
build(son[root][1],mid+1,r,val[root],R);
push(root);
}
void rebuild(int &root,double l,double r)
{
cnt=0;
pia(root);
build(root,1,cnt,l,r);
}
int Insert(int &root,double l,double r,node x)
{
if(!root)
{
val[root=++tot]=(l+r)/2;
pp[root]=x;
size[root]=1;
return root;
}
if(x==pp[root]) return root;
int ans;
double mid=(l+r)/2;
if(x<pp[root]) ans=Insert(son[root][0],l,mid,x);
else ans=Insert(son[root][1],mid,r,x);
push(root);
if(bad(root)) rebuild(root,l,r);
return ans;
}
int main()
{
int n=read(),m=read();
rt=++tot;
size[tot]=1;
val[rt]=INF/2;
val[0]=-1e9;
pp[rt]=node(0,0);
build(1,1,n);
for(int i=1;i<=n;i++) ind[i]=1;
for(int i=1;i<=m;i++)
{
scanf("%s",s);
if(s[0]=='C')
{
int l=read(),r=read(),k=read();
ind[k]=Insert(rt,0,INF,node(ind[l],ind[r]));
update(1,1,n,k);
}
else
{
int l=read(),r=read();
cout<<query(1,1,n,l,r)<<'\n';
}
}
return 0;
}
/*by DT_Kang*/
后缀平衡树
就是用平衡树维护后缀的大小。为了快速比较两后缀大小,我们采用动态维护下标的方法。由于后缀不会相等,因此点不会有重复。
bzoj4768
在后面加字符可以看做在前面加,建一个后缀平衡树,查询一个串 s 出现多少次相当于查询有多少个后缀大于等于 s 并且小于 s~ (’~’ 为无穷大字符)。平衡树里查询即可。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define ld long double
#define double long double
using namespace std;
const double INF=1e18,eps=1e-7;
const int U=6000000,N=6000010;
typedef pair <int,int> P;
struct node {
int x,y;
node(int _x=0,int _y=0) {x=_x,y=_y;}
}pp[N];
int son[N][2],size[N],b[N],ind[N],cnt,tot,rt,pre[N],Mask;
char s[N],t[N],ty[N];
double val[N];
//ind[i]:第i个后缀对应平衡树上节点
//pre[i]:平衡树上第i个点对应哪个后缀
bool operator < (node a,node b) {return (a.x^b.x)?a.x<b.x:val[a.y]<val[b.y];}
bool operator == (node a,node b) {return a.x==b.x&&a.y==b.y;}
bool operator > (node a,node b) {return (a.x^b.x)?a.x>b.x:val[a.y]>val[b.y];}
int read()
{
int x=0;char c=getchar(),flag='+';
while(!isdigit(c)) flag=c,c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return flag=='-'?-x:x;
}
void decode(int len,int Mask,char *a)
{
for(int i=0;i<len;i++) a[i]=a[i+1];
for(int j=0;j<len;j++)
{
Mask=(Mask*131+j)%len;
char t=a[j];
a[j]=a[Mask];
a[Mask]=t;
}
for(int i=len;i>=1;i--) a[i]=a[i-1];
}
bool bad(int x) {return max(size[son[x][0]],size[son[x][1]])>size[x]*0.7;}
void up(int x) {size[x]=size[son[x][0]]+size[son[x][1]]+1;}
void pia(int root)
{
if(!root) return;
pia(son[root][0]);
b[++cnt]=root;
pia(son[root][1]);
son[root][0]=son[root][1]=0;
}
void build(int &root,int l,int r,double L,double R)
{
if(l>r) return;
int mid=l+r>>1;
root=b[mid];
val[root]=(L+R)/2;
build(son[root][0],l,mid-1,L,val[root]);
build(son[root][1],mid+1,r,val[root],R);
up(root);
}
void rebuild(int &root,double l,double r)
{
cnt=0;
pia(root);
build(root,1,cnt,l,r);
}
int Insert(int &root,double l,double r,node x,int ppp)
{
if(!root)
{
size[root=++tot]=1;
pp[root]=x;
pre[root]=ppp;
val[root]=(l+r)/2;
return root;
}
double mid=(l+r)/2;
int ans=0;
if(x<pp[root]) ans=Insert(son[root][0],l,val[root],x,ppp); //注意把(l,val[root])下放而不是(l,mid)
else ans=Insert(son[root][1],val[root],r,x,ppp);
up(root);
if(bad(root)) rebuild(root,l,r);
return ans;
}
int merge(int x,int y)
{
if(!x||!y) return x|y;
if(size[x]>=size[y]) //为了合并后两个儿子较平衡
{
son[x][1]=merge(son[x][1],y);
up(x);
return x;
}
else
{
son[y][0]=merge(x,son[y][0]);
up(y);
return y;
}
}
void Erase(int &root,double l,double r,int x)
{
if(pp[root]==pp[x]) //替罪羊删除节点,合并两个子树
{
root=merge(son[root][0],son[root][1]);
return;
}
if(val[x]<val[root]) Erase(son[root][0],l,(l+r)/2,x);
else Erase(son[root][1],(l+r)/2,r,x);
if(bad(root)) rebuild(root,l,r);
up(root);
}
int cmp(int x) //暴力比较
{
int ss=U-x+1,tt=strlen(t+1);
for(int i=1;i<=min(ss,tt);i++)
{
if(s[x+i-1]<t[i]) return -1;
if(s[x+i-1]>t[i]) return 1;
}
if(ss<tt) return -1;
if(ss>tt) return 1;
return 0;
}
int query() //查询小于 t的后缀个数
{
int now=rt,ans=0;
while(now)
{
int tt=cmp(pre[now]);
if(tt==-1) ans+=size[son[now][0]]+1,now=son[now][1];
else if(tt==0) ans+=size[son[now][0]],now=son[now][1];
else if(tt==1) now=son[now][0];
}
return ans;
}
int main()
{
int q=read(),p;
scanf("%s",s+1);
int n=strlen(s+1);
for(int i=1;i<=n;i++) s[U-i+1]=s[i];
p=U-n+1;
val[rt=++tot]=INF/2;
pp[rt]=node(s[U],0);
size[tot]=1;
ind[U]=1;
pre[1]=U;
for(int i=U-1;i>=p;i--)
{
ind[i]=Insert(rt,0,INF,node(s[i],ind[i+1]),i);
}
while(q--)
{
scanf("%s",ty);
if(ty[0]=='A')
{
scanf("%s",t+1);
int n=strlen(t+1);
decode(n,Mask,t); //解压缩
for(int i=1;i<=n;i++) p--,s[p]=t[i],ind[p]=Insert(rt,0,INF,node(s[p],ind[p+1]),p);
}
else if(ty[0]=='D')
{
int tt=read();
for(int i=1;i<=tt;i++) Erase(rt,0,INF,ind[p]),p++;
}
else
{
scanf("%s",t+1);
int n=strlen(t+1),zz;
decode(n,Mask,t);
reverse(t+1,t+n+1);
int ss=query();
t[n+1]='~';
cout<<(zz=(query()-ss))<<'\n';
Mask^=zz;
}
}
return 0;
}
/*by DT_Kang*/
总结
Notice: 由于后缀平衡树插入元素是用 pair 进行比较的,pair 里存储的是平衡树中的点,要通过查权值来进行比较。因此要保证平衡树中的点的 pair 里的点也在平衡树里,这样我们才能调取大小信息。
其次是替罪羊重构常数要设小一点,以免出现精度问题。