Loading

ybtAu「高级数据结构」第5章 重量平衡树

A. 【例题1】普通平衡树

B. 【例题2】带插入区间k小值

书上给的题解是替罪羊树套权值线段树,但是这里使用块状链表套值域分块。
具体地,在每个块维护每个数出现次数的前缀和、每块数出现次数的前缀和,即 \([1,R_i]\) 区间内每个数、每块数的出现次数。

查询

查询操作,开一个值域分块用来存储区间内每块数的出现次数,开一个桶用来存储两边数字的出现次数。
首先需要把区间内整块的数塞进值域分块。找到区间端点所在块,先把两边的数塞进值域分块和桶,再处理中间这些整块,即 \([next_L,prev_R]\),差分求出每个值域块的出现次数,塞进值域分块。
接下来,遍历值域分块找到第 \(k\) 小值所在的块,接着从该块左端点开始遍历,设当前遍历到数字 \(i\),查询桶中 \(i\) 的出现次数,并差分求出 \([next_L,prev_R]\)\(i\) 的出现次数,进行判断求出第 \(k\) 小值。时间复杂度 \(O(\sqrt n+\sqrt V)\)

插入

在一个位置插入一个数,由于我们维护的是出现次数前缀和,需要修改该位置所在块到最后的所有块,把这个数的桶和值域分块 \(+1\)。时间复杂度 \(O(\sqrt n)\)
如果块长过大,需要分裂。分裂时需要将桶也复制给新块,时间复杂度 \(O(V)\)。使用 memcpy 可以加速。

修改与插入相似,这里不再赘述。

#include <iostream>
#include <vector>
#include <cstring>
#include <cassert>
#define N 100005
int n,q,a[N],id[N],lb[N],rb[N];
namespace Rope
{
	const int L=300;
	int idx;
	struct Node
	{
		Node *pre,*nxt;
		std::vector<int> vc;
		int buc[1005],b[N];
		Node() {pre=nxt=nullptr;}
		void ins(int x) {vc.push_back(x);}
		int siz() {return vc.size();}
		int& at(int x) {return vc[x];}
	} *hed,pool[1005];
	void init()
	{
		for(int i=0;i<N;i++)
		{
			id[i]=i/L;
			if(i/L*L==i) lb[id[i]]=i;
			rb[id[i]]=std::max(rb[id[i]],i);
		}
	}
	void split(Node *p)
	{
		Node *q=&pool[++idx];
		q->nxt=p->nxt,p->nxt=q,q->pre=p;
		if(q->nxt) q->nxt->pre=q;
		memcpy(q->b,p->b,sizeof q->b);
		memcpy(q->buc,p->buc,sizeof p->buc);
		for(int i=L;i<p->siz();i++)
		{
			int x=p->at(i);
			q->ins(x),p->b[x]--,p->buc[id[x]]--;
		}
		(p->vc).erase((p->vc).begin()+L,(p->vc).end());
	}
	void build()
	{
		Node *t=hed=&pool[++idx];
		for(int i=1;i<=n;i++)
		{
			t->ins(a[i]),t->b[a[i]]++,t->buc[id[a[i]]]++;
			if(t->siz()>2*L) split(t),t=t->nxt;
		}
	}
	void md(int x,int t)
	{
		x--;
		Node *p=hed;
		for(;p;p=p->nxt) {if(x<p->siz()) break;x-=p->siz();}
		//assert(p);
		int u=p->at(x);
		p->at(x)=t;
		for(;p;p=p->nxt) p->b[u]--,p->buc[id[u]]--,p->b[t]++,p->buc[id[t]]++;
	}
	void ins(int x,int t)
	{
		x--;
		Node *p=hed;
		for(;p;p=p->nxt) {if(x<p->siz()) break;x-=p->siz();}
		if(!p)
		{
			for(p=hed;p->nxt;p=p->nxt);
			p->ins(t);
		}
		else (p->vc).emplace((p->vc).begin()+x,t);
		if(p->siz()>2*L) split(p);
		for(;p;p=p->nxt) p->b[t]++,p->buc[id[t]]++;
	}
	int qr(int l,int r,int k)
	{
		l--,r--;
		Node *lc=hed,*rc=hed;
		for(;lc;lc=lc->nxt) {if(l<lc->siz()) break;l-=lc->siz();}
		for(;rc;rc=rc->nxt) {if(r<rc->siz()) break;r-=rc->siz();}
		//assert(lc),assert(rc);
		if(lc==rc)
		{
			Node *tmp=&pool[0];
			for(int i=l;i<=r;i++) tmp->b[lc->at(i)]++,tmp->buc[id[lc->at(i)]]++;
			int t=0;
			for(;;t++) {if(k>tmp->buc[t]) k-=tmp->buc[t];else break;}
			int ret=0;
			for(int i=lb[t];i<=rb[t];i++) {if(k>tmp->b[i]) k-=tmp->b[i];else {ret=i;break;}}
			for(int i=l;i<=r;i++) tmp->b[lc->at(i)]--,tmp->buc[id[lc->at(i)]]--;
			return ret;
		}
		else
		{
			Node *tmp=&pool[0];
			for(int i=l;i<lc->siz();i++) tmp->b[lc->at(i)]++,tmp->buc[id[lc->at(i)]]++;
			for(int i=0;i<=r;i++) tmp->b[rc->at(i)]++,tmp->buc[id[rc->at(i)]]++;
			int t=0,ret=0;
			for(;;t++)
			{
				int y=tmp->buc[t]+rc->pre->buc[t]-lc->buc[t];
				if(k>y) k-=y;
				else break;
			}
			for(int i=lb[t];i<=rb[t];i++)
			{
				int y=tmp->b[i]+rc->pre->b[i]-lc->b[i];
				if(k>y) k-=y;
				else {ret=i;break;}
			}
			for(int i=l;i<lc->siz();i++) tmp->b[lc->at(i)]--,tmp->buc[id[lc->at(i)]]--;
			for(int i=0;i<=r;i++) tmp->b[rc->at(i)]--,tmp->buc[id[rc->at(i)]]--;
			return ret;
		}
	}
};
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n;
	for(int i=1;i<=n;i++) std::cin>>a[i];
	Rope::init(),Rope::build();
	int la=0;
	std::cin>>q;
	for(int i=1,x,y,k;i<=q;i++)
	{
		char c;
		std::cin>>c>>x>>y;
		if(c=='Q') std::cin>>k,std::cout<<(la=Rope::qr(x^la,y^la,k^la))<<'\n';
		if(c=='M') Rope::md(x^la,y^la);
		if(c=='I') Rope::ins(x^la,y^la);
	}
}

死因:q->nxt->pre=p

C. 火星人

不要被题干迷惑了,这道题和 \(SA\) 没有半点关系。
这里还是采用块链实现。
维护每一块的前缀的哈希值,插入和修改都正常做,查询时使用倍增求解。
时间复杂度 \(O(n\sqrt n\log n)\)由于数据较水 可以通过。

#include <iostream>
#include <vector>
#define N 3005
#define M 200005
std::string s;
const unsigned long long p=1331;
unsigned long long pw[N];
int n,m,len,l2[M];
namespace Rope
{
	const int L=500;
	int idx;
	struct Node
	{
		Node *pre,*nxt;
		unsigned long long hs[N];
		std::vector<char> vc;
		Node() {pre=nxt=nullptr;}
		void ins(char x) {vc.push_back(x);}
		int siz() {return vc.size();}
		char& at(int x) {return vc[x];}
		void geths() {for(int i=0;i<vc.size();i++) hs[i]=(i?hs[i-1]*p:0)+vc[i]-'a';}
		unsigned long long hsh(int l,int r) {return hs[r]-(l?hs[l-1]:0)*pw[r-l+1];}
	} *hed,pool[10005];
	void split(Node *p)
	{
		Node *q=&pool[++idx];
		q->nxt=p->nxt,p->nxt=q,q->pre=p;
		if(q->nxt) q->nxt->pre=q;
		for(int i=L;i<p->siz();i++) q->ins(p->at(i));
		q->geths();
		(p->vc).erase((p->vc).begin()+L,(p->vc).end());
		p->geths();
	}
	unsigned long long hsh(int l,int r)
	{
		//printf("l=%d r=%d\n",l,r);
		Node *lc=hed,*rc=hed;
		unsigned long long ret=0;
		for(;lc;lc=lc->nxt)
		{
			if(l>=lc->siz()) l-=lc->siz();
			else break;
		}
		for(;rc;rc=rc->nxt)
		{
			if(r>=rc->siz()) r-=rc->siz();
			else break;
		}
		if(lc==rc) return lc->hsh(l,r);
		ret=lc->hsh(l,lc->siz()-1);
		for(Node *i=lc->nxt;i!=rc;i=i->nxt)
			ret=ret*pw[i->siz()]+i->hsh(0,i->siz()-1);
		ret=ret*pw[r+1]+rc->hsh(0,r);
		//printf("hsh %d %d = %llu\n",l,r,ret);
		return ret;
	}
	int qr(int x,int y)
	{
		x--,y--;
		int ret=0;
		for(int i=l2[std::min(len-x,len-y)];i>=0;i--)
			if(x+(1<<i)<=len&&y+(1<<i)<=len&&hsh(x,x+(1<<i)-1)==hsh(y,y+(1<<i)-1))
				ret+=1<<i,x+=1<<i,y+=1<<i;
		return ret;
	}
	void build()
	{
		Node *p=hed=&pool[++idx];
		for(int i=0;i<n;i++)
		{
			p->ins(s[i]);
			if(p->siz()>=2*L) split(p),p=p->nxt;
		}
		p->geths();
	}
	void md(int x,char t)
	{
		x--;
		Node *p;
		for(p=hed;p;p=p->nxt)
		{
			if(x>=p->siz()) x-=p->siz();
			else break;
		}
		p->at(x)=t,p->geths();
	}
	void ins(int x,char t)
	{
		len++;
		Node *p;
		for(p=hed;p;p=p->nxt)
		{
			if(x>=p->siz()) x-=p->siz();
			else break;
		}
		if(!p)
		{
			for(p=hed;p->nxt;p=p->nxt);
			p->ins(t);
		}
		else (p->vc).emplace((p->vc).begin()+x,t);
		if(p->siz()>=2*L) split(p);
		else p->geths();
	}
};
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	pw[0]=1;
	for(int i=2;i<M;i++) l2[i]=l2[i>>1]+1;
	for(int i=1;i<N;i++) pw[i]=pw[i-1]*p;
	std::cin>>s>>m,len=n=s.size();
	Rope::build();
	for(int i=1;i<=m;i++)
	{
		char op,d;
		int x,y;
		std::cin>>op>>x;
		if(op=='Q') std::cin>>y,std::cout<<Rope::qr(x,y)<<'\n';
		if(op=='R') std::cin>>d,Rope::md(x,d);
		if(op=='I') std::cin>>d,Rope::ins(x,d);
	}
}

D. 数对问题

单点修改,区间最值,需要使用线段树。
但是如果直接根据定义来求解,那么时间复杂度会炸掉。
当我们拥有了一些数对时,它们的大小关系是已知的,于是可以用一些可以快速比较的东西代替数对存在线段树里。
想到可以使用实数,具体地,建出一棵二叉树,令根节点管辖的区间为 \([L,R]\),该节点的值为 \(\frac{L+R}2\);当新插入一个数时,如果大于根,那么放到右面,区间变为 \([\frac{L+R}2,R]\),否则放到左面,区间变为 \([L,\frac{L+R}2]\)。这样能保证得到的实数的大小关系与数对的大小关系是一样的。
发现如果深度过大那么精度会炸,所以需要使用平衡树。
如果使用一些通过旋转来维持平衡的树,比如 Treap,那么由于树的形态不断变化,每个点的实数也在不断变化,而每次更新这些实数的复杂度是无法接受的。所以考虑树形态较稳定的树。
可以使用替罪羊树实现。

#include <iostream>
#include <vector>
#define N 500005
int n,m,pos[N],RT,rt;
double wt[N];
const double alpha=0.75;
struct rp
{
	int l,r;
	bool operator <(const rp &g) const {return (wt[l]!=wt[g.l])?(wt[l]<wt[g.l]):(wt[r]<wt[g.r]);}
	bool operator ==(const rp &g) const {return l==g.l&&r==g.r;}
};
namespace SGT
{
	int idx;
	struct Node
	{
		rp val;
		int siz;
	} tr[N];
	int ls[N],rs[N];
	std::vector<int> vc;
	void pu(int x) {tr[x].siz=tr[ls[x]].siz+tr[rs[x]].siz+1;}
	void toli(int x)
	{
		if(!x) return;
		toli(ls[x]),vc.push_back(x),toli(rs[x]);
	}
	int build(int l,int r,double L,double R)
	{
		if(l>r) return 0;
		int mid=l+r>>1;
		double Mid=(L+R)/2.0;
		wt[vc[mid]]=Mid;
		ls[vc[mid]]=build(l,mid-1,L,Mid),rs[vc[mid]]=build(mid+1,r,Mid,R);
		return pu(vc[mid]),vc[mid];
	}
	bool chk(int x) {return std::max(tr[ls[x]].siz,tr[rs[x]].siz)>tr[x].siz*alpha+5;}
	void rebuild(int &x,double L,double R)
	{
		vc.clear();
		toli(x);
		x=build(0,vc.size()-1,L,R);
	}
	int ins(int &x,rp t,double L,double R)
	{
		double Mid=(L+R)/2.0;
		if(!x) return x=++idx,wt[x]=Mid,tr[x].val=t,tr[x].siz=1,x;
		if(t==tr[x].val) return x;
		tr[x].siz++;
		int ret=(t<tr[x].val)?ins(ls[x],t,L,Mid):ins(rs[x],t,Mid,R);
		if(chk(x)) rebuild(x,L,R);
		return ret;
	}
};
namespace sgt
{
	int d[N],ls[N],rs[N],idx;
	#define mid (lb+rb>>1)
	int cmax(int x,int y) {return (wt[pos[x]]>=wt[pos[y]])?x:y;}
	void md(int &x,int t,int lb,int rb)
	{
		if(!x) x=++idx;
		if(lb==rb) return (void)(d[x]=lb);
		(t<=mid)?md(ls[x],t,lb,mid):md(rs[x],t,mid+1,rb);
		d[x]=cmax(d[ls[x]],d[rs[x]]);
	}
	int qr(int x,int l,int r,int lb,int rb)
	{
		if(l<=lb&&rb<=r) return d[x];
		if(r<=mid) return qr(ls[x],l,r,lb,mid);
		if(l>mid) return qr(rs[x],l,r,mid+1,rb);
		return cmax(qr(ls[x],l,r,lb,mid),qr(rs[x],l,r,mid+1,rb));
	}
	#undef mid
};
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n>>m;
	SGT::ins(RT,{0,0},0,10);
	for(int i=1;i<=n;i++) pos[i]=RT;
	for(int i=1;i<=n;i++) sgt::md(rt,i,1,n);
	for(int i=1,l,r,x;i<=m;i++)
	{
		char op;
		std::cin>>op>>l>>r;
		if(op=='C')
		{
			std::cin>>x;
			pos[x]=SGT::ins(RT,{pos[l],pos[r]},0,10);
			sgt::md(rt,x,1,n);
		}
		if(op=='Q') std::cout<<sgt::qr(rt,l,r,1,n)<<'\n';
	}
}
posted @ 2025-06-30 15:51  整齐的艾萨克  阅读(20)  评论(0)    收藏  举报