重量平衡樹
什麼是重量平衡樹?
在插入/刪除操作之後,爲了保持樹的平衡而重構的子樹大小爲均攤/期望 。
比如:替罪羊樹,treap。
動態下標
平衡樹上判斷兩個點的大小是一個 的。
怎麼更快地做這件事呢?
給每個點一個標號, 序上靠前的標號小。設一個節點 對應的區間是 ,令它的標號爲 。讓它的左右兒子對應的區間爲 和 ,然後遞歸計算標號。
但是旋轉操作會影響標號的分配。所以用重量平衡樹做這個東西。
BZOJ3600 沒有人的算術
如果是普通的數,用線段樹即可。
問題在於區間合併的時候如何比較兩個數大小。這就是動態下標平衡樹的應用。平衡樹裏的每個點存兩個信息,一個是這個點的權值,另一個是一個 pair,表示它是由哪兩個點合成的(是點標號不是權值),pair 是用於插入平衡樹的比較。爲了維護 pair 內元素的有序性,我們的平衡樹只插入不刪除。
複雜度 。
#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 裏的點也在平衡樹裏,這樣我們才能調取大小信息。
其次是替罪羊重構常數要設小一點,以免出現精度問題。