一類平衡樹問題

重量平衡樹

什麼是重量平衡樹?
在插入/刪除操作之後,爲了保持樹的平衡而重構的子樹大小爲均攤/期望 O(nlogn)O(nlogn)
比如:替罪羊樹,treap。

動態下標

平衡樹上判斷兩個點的大小是一個 log\log 的。
怎麼更快地做這件事呢?
給每個點一個標號,dfsdfs 序上靠前的標號小。設一個節點 xx 對應的區間是 (l,r)(l,r),令它的標號爲 mid=(l+r)/2mid=(l+r)/2。讓它的左右兒子對應的區間爲 (l,mid)(l,mid)(mid,r)(mid,r),然後遞歸計算標號。

但是旋轉操作會影響標號的分配。所以用重量平衡樹做這個東西。

BZOJ3600 沒有人的算術
如果是普通的數,用線段樹即可。
問題在於區間合併的時候如何比較兩個數大小。這就是動態下標平衡樹的應用。平衡樹裏的每個點存兩個信息,一個是這個點的權值,另一個是一個 pair,表示它是由哪兩個點合成的(是點標號不是權值),pair 是用於插入平衡樹的比較。爲了維護 pair 內元素的有序性,我們的平衡樹只插入不刪除。
複雜度 O(qlogn)O(q\log n)

#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 裏的點也在平衡樹裏,這樣我們才能調取大小信息。
其次是替罪羊重構常數要設小一點,以免出現精度問題。

發佈了89 篇原創文章 · 獲贊 26 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章