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';
}
}

浙公网安备 33010602011771号