树模板
线段树
线段树
-
思想
将整个区间划分成均等两半,对于每一半继续向下分为两段,直到分到单点。
每一段区间都维护一个区间内的答案(或者别的什么和答案有关的东西,比如线性基),然后在查询区间答案时就可以用不超过 \(\log{n}\) 个区间凑出查询区间的答案。
所有的修改可以通过懒标记下分到几个区间来记录,直到查询或另一次修改需要深入这段较大的区间时,再下传 (\(pushdown\))。
每次修改操作完成后需要向上面更大的区间传答案 (\(pushup\))。
由于每个点的坐标是直接 \(<<1\) 或 \(<<1|1\) 算出来的,可能存在浪费区间的情况,一般树的数组需要多开几倍。
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
const int N=1e5+10;
#define ll long long
int n,m;
int a[N];
struct memr{
int l,r;
ll sum;
int tg,siz;
}tr[N<<4];
void pushup(int p){
tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
return ;
}
void pushdown(int p){
if(tr[p].tg){
tr[p<<1].sum+=1ll*tr[p<<1].siz*tr[p].tg;
tr[p<<1|1].sum+=1ll*tr[p<<1|1].siz*tr[p].tg;
tr[p<<1].tg+=tr[p].tg;
tr[p<<1|1].tg+=tr[p].tg;
tr[p].tg=0;
}
return ;
}
void build(int p,int l,int r){
tr[p].l=l,tr[p].r=r;
tr[p].siz=r-l+1;
if(l==r){
tr[p].sum=a[l];
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
return ;
}
void change(int p,int l,int r,int v){
if(l<=tr[p].l && tr[p].r<=r){
tr[p].sum+=1ll*v*tr[p].siz;
tr[p].tg+=v;
return ;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) change(p<<1,l,r,v);
if(mid<r) change(p<<1|1,l,r,v);
pushup(p);
return ;
}
ll ask(int p,int l,int r){
if(l<=tr[p].l && tr[p].r<=r){
return tr[p].sum;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
ll cnt=0;
if(l<=mid) cnt+=ask(p<<1,l,r);
if(mid<r) cnt+=ask(p<<1|1,l,r);
pushup(p);
return cnt;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;++i)
a[i]=read();
build(1,1,n);
int opt,l,r,d;
while(m--){
opt=read(),l=read(),r=read();
if(opt==1){
d=read();
change(1,l,r,d);
}
else printf("%lld\n",ask(1,l,r));
}
return 0;
}
李超线段树
-
好用,但没什么地方用(
-
思想
把普通线段树维护的 \(sum\)、\(max\) 等值变为一条直线 \(kx+b\) 的 \(k\) 和 \(b\)。
一般的操作是在某区间加入一条直线、查询某单点的最大/最小值。
写法有很多种,这里写的是个人认为比较好写也比较好记的一种方式。- 对于操作:与线段树一样,下分到 \(\log{n}\) 个区间。但和线段树不同的是,它并非下分到 \(\log{n}\) 个区间后就能直接对答案进行操作,而是需要对这个区间两个端点计算值,如果当前直线与记录的直线在区间内相交了,还需继续下分,直到某个区间内不能更改或全部被覆盖为新线段。
- 对于查询,每次查单点所在所有线段树区间,计算答案即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
const double eps=1e-8;
struct memr{
int l,r;
// double mx;
double k,b;
}tr[N<<4];
int n;
double count(int x,double k,double b){
return 1.0*x*k+b;
}
inline void build(int p,int l,int r){
tr[p].l=l,tr[p].r=r;
tr[p].k=tr[p].b=0;
if(l==r) return ;
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
return ;
}
void change(int p,int l,int r,double k,double b){
if(l<=tr[p].l && tr[p].r<=r){
if(!tr[p].k && !tr[p].b){
tr[p].k=k,tr[p].b=b;
return ;
}
double l1=count(tr[p].l,tr[p].k,tr[p].b);
double r1=count(tr[p].r,tr[p].k,tr[p].b);
double l2=count(tr[p].l,k,b);
double r2=count(tr[p].r,k,b);
if(l1<=l2 && r1<=r2){
tr[p].k=k,tr[p].b=b;
return ;
}
if(l2<=l1 && r2<=r1)
return ;
}
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) change(p<<1,l,r,k,b);
if(mid<r) change(p<<1|1,l,r,k,b);
return ;
}
double ask(int p,int x,double ans){
ans=max(ans,tr[p].k*x*1.0+tr[p].b);
if(tr[p].l==x && tr[p].r==x)
return ans;
int mid=(tr[p].l+tr[p].r)>>1;
if(x<=mid) return ask(p<<1,x,ans);
return ask(p<<1|1,x,ans);
}
int main(){
// freopen("company.in","r",stdin);
// freopen("company.out","w",stdout);
scanf("%d",&n);
build(1,1,5e4);
string opt;
double x,y;
int t;
while(n--){
cin>>opt;
if(opt[0]=='Q'){
scanf("%d",&t);
printf("%d\n",(int)floor(1.0*ask(1,t,0)/100.0));
}
else{
scanf("%lf%lf",&x,&y);
change(1,1,5e4,y,x-y);
}
}
return 0;
}
*动态开点
-
有一种情况是,我们要对一个很大的范围建立线段树,如果按正常线段树开空间会炸;而其中并不是每一个点都需要用到的时候,我们使用动态开点来减少空间的浪费。
-
相当于将原来可以直接计算的 \(p<<1\) 和 \(p<<1|1\) 关系断开,手动设置左右儿子,直到需要这个点的时候才新建这个点(但每个点对应的区间还是从上面按原规律传下来的)。
主席树/可持久化线段树
-
一种支持查询历史版本的树状结构(可持久化一般就指可以查询历史版本的数据结构)
-
如果对于每一个历史版本都建一棵树,那么空间的浪费将会非常巨大。
考虑到每一次操作(单点修改),只会走一条链上的点,所以我们只需要对这些被改过的点建立这个历史版本,即每次只新增一条链即可。
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
const int N=2e5+10;
struct memr{
int ls,rs;
int val;
}tr[N<<5];
int n,m,cnt=0;
int a[N],b[N],bl;
int rt[N];
int build(int l,int r){
int p=++cnt;
tr[p].val=0;
if(l==r) return p;
int mid=(l+r)>>1;
tr[p].ls=build(l,mid);
tr[p].rs=build(mid+1,r);
return p;
}
int change(int p,int x,int l,int r){
// cerr<<p<<" "<<x<<" "<<l<<" "<<r<<endl;
int d=++cnt;
tr[d].ls=tr[p].ls,tr[d].rs=tr[p].rs;
tr[d].val=tr[p].val+1;
if(l==r) return d;
int mid=(l+r)>>1;
if(x<=mid) tr[d].ls=change(tr[p].ls,x,l,mid);
else tr[d].rs=change(tr[p].rs,x,mid+1,r);
return d;
}
int ask(int l,int r,int k,int c,int d){
if(c>=d) return c;
int x=tr[tr[r].ls].val-tr[tr[l].ls].val;
int mid=(c+d)>>1;
if(x<k) return ask(tr[l].rs,tr[r].rs,k-x,mid+1,d);
return ask(tr[l].ls,tr[r].ls,k,c,mid);
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;++i)
a[i]=read(),b[i]=a[i];
sort(b+1,b+n+1);
bl=unique(b+1,b+n+1)-b-1;
rt[0]=build(1,bl);
for(int i=1;i<=n;++i){
int t=lower_bound(b+1,b+bl+1,a[i])-b;
rt[i]=change(rt[i-1],t,1,bl);
}
int l,r,k;
while(m--){
l=read(),r=read(),k=read();
printf("%d\n",b[ask(rt[l-1],rt[r],k,1,bl)]);
}
return 0;
}
树链剖分
-
思想
主要用于处理对树上路径修改查询。
将每个节点的子树最大的儿子称为重儿子,每个非叶子节点有且只有一个重儿子。对于一条全由重儿子构成的链叫做重链。对每一个重链记录其链顶。
将树上节点按 \(dfs\) 序重编号之后,容易发现每一个子树属于相邻的一段区间。于是在深搜时优先处理重儿子,这样可以使得每一条重链都在一个连续区间。
重链剖分完成后,容易发现这棵树变成了若干条重链,每一条重链都由链顶与它父亲的一条轻边相互连接,因此可以把树上路径转化成对若干条重链的修改查询操作。
然后对重编号后的序列建线段树即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,r,mod;
struct memr{
int ls,rs,v;
int siz,tg;
}tr[N];
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
int f[N],b[N],dep[N],son[N];//son重儿子
int size[N],top[N],ind[N],val[N];//size子树大小,ind转换后节点编号对应
int head[N<<2],ver[N<<2],nxt[N<<2],tot;//存树
int cnt=0,a[N];
void add(int x,int y){//加边
ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
ver[++tot]=x,nxt[tot]=head[y],head[y]=tot;
}
void get_son(int p,int fa,int de){//求重儿子、重链、(父亲
f[p]=fa;
dep[p]=de;
for(int i=head[p];i;i=nxt[i]){
if(ver[i]==fa) continue;
get_son(ver[i],p,de+1);
size[p]+=size[ver[i]];
if(size[ver[i]]>=size[son[p]])
son[p]=ver[i];
}
size[p]++;
return ;
}
void cont(int p,int t){//连接重链、记录节点编号对应关系
ind[p]=++cnt;
top[p]=t;
a[cnt]=val[p];
// if(!son[p]) return;
if(son[p]) cont(son[p],t);
for(int i=head[p];i;i=nxt[i]){
if(ver[i]==son[p] || ver[i]==f[p])
continue;
cont(ver[i],ver[i]);
}
return ;
}
//以下为线段树
void pushup(int p){
tr[p].v=tr[p<<1].v+tr[p<<1|1].v;
return ;
}
void pushdown(int p){
if(tr[p].tg){
(tr[p<<1].v+=tr[p].tg*tr[p<<1].siz)%=mod;
(tr[p<<1|1].v+=tr[p].tg*tr[p<<1|1].siz)%=mod;
(tr[p<<1].tg+=tr[p].tg)%=mod;
(tr[p<<1|1].tg+=tr[p].tg)%=mod;
tr[p].tg=0;
}
return ;
}
void change(int p,int l,int r,int t){
if(l<=tr[p].ls && tr[p].rs<=r){
(tr[p].v+=(tr[p].siz*t)%mod)%=mod;
tr[p].tg+=t;
return ;
}
pushdown(p);
int mid=(tr[p].ls+tr[p].rs)>>1;
if(l<=mid) change(p<<1,l,r,t);
if(r>mid) change(p<<1|1,l,r,t);
pushup(p);
return ;
}
int ask(int p,int l,int r){
if(l<=tr[p].ls && tr[p].rs<=r)
return tr[p].v;
pushdown(p);
int mid=(tr[p].ls+tr[p].rs)>>1;
int ans=0;
if(l<=mid) ans+=ask(p<<1,l,r)%mod;
if(r>mid) ans+=ask(p<<1|1,l,r)%mod;
return ans%mod;
}
//以上线段树
void change_line(int x,int y,int z){//原树上更改
int a=top[x],b=top[y];
while(a!=b){
if(dep[a]<dep[b])
swap(a,b),swap(x,y);
change(1,ind[a],ind[x],z);
x=f[a],a=top[x];
}
if(ind[x]>ind[y]) swap(x,y);
change(1,ind[x],ind[y],z);
return ;
}
int ask_line(int x,int y){//原树上查询
int a=top[x],b=top[y],ans=0;
while(a!=b){
if(dep[a]<dep[b])
swap(a,b),swap(x,y);
(ans+=ask(1,ind[a],ind[x]))%=mod;
x=f[a],a=top[x];
}
if(ind[x]>ind[y]) swap(x,y);
(ans+=ask(1,ind[x],ind[y]))%mod;
return ans%mod;
}
void build(int p,int l,int r){//建线段树
tr[p].ls=l,tr[p].rs=r;
tr[p].siz=r-l+1;
if(l==r){
tr[p].v=a[l];
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
return ;
}
int main(){
// freopen("P3384_1.in","r",stdin);
n=read(),m=read(),r=read(),mod=read();
for(int i=1;i<=n;++i)
val[i]=read();
int x,y;
for(int i=1;i<n;++i){
x=read(),y=read();
add(x,y);
}
f[r]=1,dep[r]=0;
get_son(r,0,1);
cont(r,r);
build(1,1,n);
int opt,z;
for(int i=1;i<=m;++i){
opt=read(),x=read();
if(opt==4)
printf("%d\n",ask(1,ind[x],ind[x]+size[x]-1)%mod);
else{
y=read();
if(opt==1){
z=read();
change_line(x,y,z);
}
else if(opt==2)
printf("%d\n",ask_line(x,y));
else
change(1,ind[x],ind[x]+size[x]-1,y);
}
}
return 0;
}
平衡树
- 一种维护一个大小关系(权值、排名等)的树状结构。一般使用二叉树结构来实现。
一般的规律:\(key_{ls_x}<key_x<key_{rs_x}\);
Treap
-
最普通的一种平衡树。
依靠左旋和右旋来维护平衡。
判断左右旋的依据是随机数,理论复杂度是 \(\log{n}\) 级别的。但不排除一种可能是运气非常好,以至于旋转完了之后生成了一条链 -
支持插入数字、删除数字、查排名、查数、查前驱、查后继等操作。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,Inf=0x7fffffff;
int root,k=0;
struct memr{
int l,r,val;
int sum,dat,size;
//sum:the number of points value val
//dat:a random number to make the tree seemingly equal
//size:the size of the point and its sons
}tr[N];
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
void update(int p){//
tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+tr[p].sum;
}
int _New(int v){//build a new point
tr[++k].val=v;
tr[k].dat=rand();
tr[k].sum=tr[k].size=1;
return k;
}
int get_rank(int p,int v){//get the rank of v
if(p==0) return 0;
if(tr[p].val==v)
return tr[tr[p].l].size+1;
if(v<tr[p].val)
return get_rank(tr[p].l,v);
return get_rank(tr[p].r,v)+tr[tr[p].l].size+tr[p].sum;
}
int get_number(int p,int rank){//get the val by rank
if(p==0) return Inf;
if(tr[tr[p].l].size>=rank)
return get_number(tr[p].l,rank);
if(tr[tr[p].l].size+tr[p].sum>=rank)
return tr[p].val;
return get_number(tr[p].r,rank-tr[tr[p].l].size-tr[p].sum);
}
void zig(int &p){//turn right
int q=tr[p].l;
tr[p].l=tr[q].r,tr[q].r=p,p=q;
update(tr[p].r),update(p);
}
void zag(int &p){//turn left
int q=tr[p].r;
tr[p].r=tr[q].l,tr[q].l=p,p=q;
update(tr[p].l),update(p);
}
int _Last(int v){//the one in front of v
int ans=1;
int p=root;
while(p){
if(v==tr[p].val){
if(tr[p].l){
p=tr[p].l;
while(tr[p].r)
p=tr[p].r;
ans=p;
}
break;
}
if(tr[p].val<v && tr[p].val>tr[ans].val) ans=p;
p=(v<tr[p].val)?tr[p].l:tr[p].r;
}
return tr[ans].val;
}
int _Next(int v){//the one behand v
int ans=2;
int p=root;
while(p){
if(v==tr[p].val){
if(tr[p].r){
p=tr[p].r;
while(tr[p].l)
p=tr[p].l;
ans=p;
}
break;
}
if(tr[p].val>v && tr[p].val<tr[ans].val) ans=p;
p=(v<tr[p].val)?tr[p].l:tr[p].r;
}
return tr[ans].val;
}
void insert(int &p,int v){//insert a number
if(p==0){
p=_New(v);
return ;
}
if(tr[p].val==v){
tr[p].sum++;
update(p);
}
else if(tr[p].val>v){
insert(tr[p].l,v);
if(tr[p].dat<tr[tr[p].l].dat)
zig(p);
}
else if(tr[p].val<v){
insert(tr[p].r,v);
if(tr[p].dat<tr[tr[p].r].dat)
zag(p);
}
update(p);
return ;
}
void remove(int &p,int v){//delete v
if(p==0) return ;
if(tr[p].val==v){
if(tr[p].sum>1){
tr[p].sum--,update(p);
return ;
}
if(tr[p].l || tr[p].r){
if(tr[p].r==0 || tr[tr[p].l].dat>tr[tr[p].r].dat)
zig(p),remove(tr[p].r,v);
else zag(p),remove(tr[p].l,v);
update(p);
}
else p=0;
return ;
}
(tr[p].val<v)?remove(tr[p].r,v):remove(tr[p].l,v);
update(p);
return ;
}
void build(){//build a new tree
_New(-Inf),_New(Inf);
root=1,tr[1].r=2;
update(root);
}
int main(){
srand(time(0));
n=read();
build();
int opt,x;
while(n--){
opt=read(),x=read();
if(opt==1)//insert a point
insert(root,x);
else if(opt==2)//delete a point
remove(root,x);
else if(opt==3)//get the index of x
printf("%d\n",get_rank(root,x)-1);
else if(opt==4)//get No.x point
printf("%d\n",get_number(root,x+1));
else if(opt==5)
printf("%d\n",_Last(x));
else
printf("%d\n",_Next(x));
}
return 0;
}
fhq Treap
-
没有旋转操作,但维护平衡的依据仍然是随机数。
也就是说这仍然是一个考验运气的算法
维护形状依靠的是分裂和合并操作。 -
同样支持普通 \(\mathtt{Treap}\) 的操作,同时能够非常方便地进行区间删除。
-
闲话:使用体验:
fhq,我永远的神
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
const int N=1e5+10;
const int Inf=1e9+10;
struct memr{
int ls,rs;
int val,dat;
int siz,num;
}tr[N];
int n;
int cnt=0;
int rt,a,b,c;
int _New(int v){
++cnt;
// tr[cnt].ls=tr[cnt].rs=0;
tr[cnt].dat=rand();
tr[cnt].val=v;
tr[cnt].siz=1;
return cnt;
}
void update(int p){
tr[p].siz=tr[tr[p].ls].siz+tr[tr[p].rs].siz+1;
return ;
}
void split(int p,int v,int &x,int &y){
// printf("split:%d %d\n",p,v);
if(!p){
x=0,y=0;
return ;
}
if(tr[p].val<=v){
x=p;
split(tr[p].rs,v,tr[p].rs,y);
}
else{
y=p;
split(tr[p].ls,v,x,tr[p].ls);
}
update(p);
return ;
}
int merge(int x,int y){
// printf("merge:%d %d\n",x,y);
if(!x || !y) return x+y;
if(tr[x].dat>tr[y].dat){
tr[x].rs=merge(tr[x].rs,y);
update(x);
return x;
}
tr[y].ls=merge(x,tr[y].ls);
update(y);
return y;
}
void insert(int v){
// printf("insert:%d\n",v);
split(rt,v,a,b);
rt=merge(merge(a,_New(v)),b);
return ;
}
void delet(int v){
// printf("delete:%d\n",v);
split(rt,v,a,c);
split(a,v-1,a,b);
b=merge(tr[b].ls,tr[b].rs);
rt=merge(merge(a,b),c);
return ;
}
int valu(int k){
// printf("value:%d\n",k);
int p=rt;
while(p){
if(tr[tr[p].ls].siz+1==k)
break;
else if(tr[tr[p].ls].siz<k){
k-=tr[tr[p].ls].siz+1;
p=tr[p].rs;
}
else p=tr[p].ls;
}
return tr[p].val;
}
int rnk(int v){
// printf("rank:%d\n",v);
split(rt,v-1,a,b);
int ans=tr[a].siz+1;
rt=merge(a,b);
return ans;
}
int last(int v){
// printf("last:%d\n",v);
split(rt,v-1,a,b);
int p=a;
while(tr[p].rs) p=tr[p].rs;
int ans=tr[p].val;
rt=merge(a,b);
return ans;
}
int nxt(int v){
// printf("next:%d\n",v);
split(rt,v,a,b);
int p=b;
while(tr[p].ls) p=tr[p].ls;
int ans=tr[p].val;
rt=merge(a,b);
return ans;
}
int main(){
// freopen("P3369_3.in","r",stdin);
srand(time(0));
n=read();
int opt,t;
while(n--){
opt=read(),t=read();
if(opt==1)
insert(t);
else if(opt==2)
delet(t);
else if(opt==3)
printf("%d\n",rnk(t));
else if(opt==4)
printf("%d\n",valu(t));
else if(opt==5)
printf("%d\n",last(t));
else printf("%d\n",nxt(t));
// cerr<<opt<<endl;
}
return 0;
}
-
fhq treap 拓展功能
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
#define ll long long
const int N=5e5+10;
struct memr{
int ls,rs;//左 &右儿子
int dat,siz;//堆序 &子树大小
int val,sum,lmx,rmx,mx;//值 &和 &左端最大子段 &右端最大子段 &区间最大子段
int ph,fl;//推平 &翻转标记
#define ls(i) tr[i].ls
#define rs(i) tr[i].rs
}tr[N];
int n,m,rt=0,cnt=0;
int a,b,c;
int rec[N],tot=0;//回收
int _New(int v){//新建节点
int p=tot>0?rec[tot--]:++cnt;
memset(tr+p,0,sizeof(memr));
tr[p].sum=tr[p].val=tr[p].mx=v;
tr[p].lmx=tr[p].rmx=max(0,v);
tr[p].siz=1;
tr[p].dat=rand();
tr[p].fl=tr[p].ph=0;
return p;
}
void pushup(int p){
if(!p) return ;
tr[p].siz=tr[ls(p)].siz+tr[rs(p)].siz+1;
tr[p].sum=tr[ls(p)].sum+tr[rs(p)].sum+tr[p].val;
tr[p].lmx=max(0,max(tr[ls(p)].lmx,tr[ls(p)].sum+tr[p].val+tr[rs(p)].lmx));
tr[p].rmx=max(0,max(tr[rs(p)].rmx,tr[rs(p)].sum+tr[p].val+tr[ls(p)].rmx));
tr[p].mx=max(tr[ls(p)].rmx+tr[rs(p)].lmx,0)+tr[p].val;
if(ls(p)) tr[p].mx=max(tr[p].mx,tr[ls(p)].mx);
if(rs(p)) tr[p].mx=max(tr[p].mx,tr[rs(p)].mx);
// printf("up:%d %d %d %d\n",p,ls(p),rs(p),tr[p].sum);
return ;
}
void cover(int p,int v){//推平子树
if(!p) return ;
tr[p].val=v;
tr[p].sum=tr[p].siz*v;
tr[p].lmx=tr[p].rmx=max(0,tr[p].sum);
tr[p].mx=max(v,tr[p].sum);
tr[p].ph=1;
return ;
}
void reverse(int p){//子树翻转
if(!p) return ;
swap(ls(p),rs(p));
swap(tr[p].lmx,tr[p].rmx);
tr[p].fl^=1;
return ;
}
void pushdown(int p){//下传标记
if(!p) return ;
if(tr[p].ph){
cover(ls(p),tr[p].val);
cover(rs(p),tr[p].val);
tr[p].ph=0;
}
if(tr[p].fl){
reverse(ls(p));
reverse(rs(p));
tr[p].fl=0;
}
return ;
}
void split(int p,int k,int &x,int &y){
if(!p){
x=y=0;
return ;
}
pushdown(p);
if(tr[ls(p)].siz<k){
x=p;
split(rs(p),k-tr[ls(p)].siz-1,rs(p),y);
}
else{
y=p;
split(ls(p),k,x,ls(p));
}
pushup(p);
return ;
}
int merge(int x,int y){//合并两棵树
if(!x || !y) return x+y;
if(tr[x].dat>tr[y].dat){
pushdown(x);
rs(x)=merge(rs(x),y);
pushup(x);
return x;
}
pushdown(y);
ls(y)=merge(x,ls(y));
pushup(y);
return y;
}
int in[N];
#define mid ((l+r)>>1)
int add(int l,int r){//对一个区间建树
if(l!=r)
return merge(add(l,mid),add(mid+1,r));
return _New(in[l]);
}
void insert(int pos,int k){//在pos后插入k个数
// cerr<<k<<endl;
for(int i=1;i<=k;++i)
in[i]=read();
split(rt,pos,a,b);
rt=merge(merge(a,add(1,k)),b);
return ;
}
void recover(int p){//回收废点
if(!p) return ;
rec[++tot]=p;
recover(ls(p));
recover(rs(p));
return ;
}
void delet(int l,int r){//区间删除 l~r
split(rt,r,a,c);
split(a,l-1,a,b);
recover(b);
rt=merge(a,c);
return ;
}
void push(int l,int r,int x){//推平区间
split(rt,r,a,c);
split(a,l-1,a,b);
cover(b,x);
rt=merge(merge(a,b),c);
return ;
}
void rv(int l,int r){//区间翻转
split(rt,r,a,c);
split(a,l-1,a,b);
reverse(b);
rt=merge(merge(a,b),c);
return ;
}
int asksum(int l,int r){//区间和
split(rt,r,a,c);
split(a,l-1,a,b);
int ans=tr[b].sum;
rt=merge(merge(a,b),c);
return ans;
}
int main(){
// freopen("P2042_2.in","r",stdin);
srand(time(0));
n=read(),m=read();
insert(0,n);
string opt;
int pos,len,x;
while(m--){
cin>>opt;
if(opt=="INSERT"){
pos=read(),len=read();
insert(pos,len);
}
else if(opt=="DELETE"){
pos=read(),len=read();
delet(pos,pos+len-1);
}
else if(opt=="MAKE-SAME"){
pos=read(),len=read(),x=read();
push(pos,pos+len-1,x);
}
else if(opt=="REVERSE"){
pos=read(),len=read();
rv(pos,pos+len-1);
}
else if(opt=="GET-SUM"){
pos=read(),len=read();
printf("%d\n",asksum(pos,pos+len-1));
}
else printf("%d\n",tr[rt].mx);
// cerr<<opt<<endl;
// cerr<<cnt<<" "<<tot<<endl;
// printf("sum:%d\n",tr[rt].sum);
}
return 0;
}
Splay
-
把普通 \(\mathtt{Treap}\) 的左旋和右旋合并成了一个操作。不依靠随机数来维护平衡。
-
同样支持普通 \(\mathtt{Treap}\) 的操作,还可以指定根节点。
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
const int N=1e5+10;
const int Inf=1<<30;
int n,m,root,k=0;
int a[N];
struct memr{
int son[2];
int val,siz,tg,fr;
}tr[N];
void pushup(int p){
tr[p].siz=tr[tr[p].son[0]].siz+tr[tr[p].son[1]].siz+1;
return ;
}
void pushdown(int p){
if(p && tr[p].tg){
tr[tr[p].son[0]].tg^=1;
tr[tr[p].son[1]].tg^=1;
swap(tr[p].son[0],tr[p].son[1]);
tr[p].tg=0;
}
return ;
}
void rotate(int x){
int y=tr[x].fr;
int z=tr[y].fr;
pushdown(x),pushdown(y);
int f=(x==tr[y].son[1]);
tr[y].son[f]=tr[x].son[f^1];
tr[tr[y].son[f]].fr=y;
tr[z].son[(tr[z].son[1]==y)]=x;
tr[x].fr=z;
tr[x].son[f^1]=y;
tr[y].fr=x;
pushup(y);
return ;
}
void Splay(int x,int goal){
for(int i=tr[x].fr;i!=goal;rotate(x),i=tr[x].fr){
// cout<<x<<" "<<i<<endl;
if(tr[i].fr!=goal){
int a=(x==tr[tr[x].fr].son[1]);
int b=(i==tr[tr[i].fr].son[1]);
(a==b)?rotate(i):rotate(x);
}
}
if(!goal) root=x;
return ;
}
int find(int x){
int y=root;
while(1){
pushdown(y);
if(x<=tr[tr[y].son[0]].siz){
y=tr[y].son[0];
continue;
}
x-=tr[tr[y].son[0]].siz+1;
if(!x) return y;
y=tr[y].son[1];
}
}
void rev(int l,int r){
int a=find(l-1),b=find(r+1);
// cout<<a<<" "<<b<<endl;
Splay(a,0);
Splay(b,a);//顺序误我
// puts("Splay Over.");
int p=tr[tr[root].son[1]].son[0];
tr[p].tg^=1;
return ;
}
int build(int l,int r,int f){
if(l>r) return 0;
int p=++k;
tr[p].siz=1;
tr[p].tg=0;
tr[p].fr=f;
int mid=(l+r)>>1;
tr[p].val=a[mid];
tr[p].son[0]=build(l,mid-1,p);
tr[p].son[1]=build(mid+1,r,p);
pushup(p);//pushup好
return p;
}
void print(int p){
if(!p) return;
pushdown(p);
print(tr[p].son[0]);
if(tr[p].val!=Inf && tr[p].val!=-Inf)
printf("%d ",tr[p].val);
print(tr[p].son[1]);
return ;
}
int main(){
n=read(),m=read();
int l,r;
a[1]=-Inf,a[n+2]=Inf;
for(int i=1;i<=n;++i)
a[i+1]=i;
root=build(1,n+2,0);
for(int i=1;i<=m;++i){
l=read(),r=read();
rev(l+1,r+1);
}
print(root);
return 0;
}
替罪羊树
-
一种思想非常暴力的平衡树。
定义 \(\alpha\) 为节点 \(x\) 中较大的那个儿子的大小在 \(x\) 的大小中的占比,最平衡的情况是完全二叉树的 \(\alpha=0.5\),最不平衡的情况是链的 \(\alpha=1\)。
一般人为界定 \(\alpha=0.5\sim 0.75\) 左右。 -
只要扫到某个节点的 \(\alpha\) 值超过界限,就把这个节点及其子树全部摧毁,重构成一棵完全二叉树树再接回来。
因为是惰性删除(直接打个标记,但这个点实际上还存在),所以统计节点大小时的情况要多一点。 -
支持普通 \(Treap\) 的操作。但查前驱和后继不需要单独写函数。
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
#define alpha 0.75
const int N=1e5+10;
int n,cnt=0,rt;
struct memr{
int ls,rs;
int v,dt;
int s,sz,sd;
}tr[N<<1];
void update(int p){
tr[p].s=tr[tr[p].ls].s+tr[tr[p].rs].s+1;
tr[p].sz=tr[tr[p].ls].sz+tr[tr[p].rs].sz+tr[p].dt;
tr[p].sd=tr[tr[p].ls].sd+tr[tr[p].rs].sd+(tr[p].dt!=0);
return ;
}
bool ifrub(int p){//判断是否重构
if(!tr[p].dt) return 0;
if(1.0*alpha*tr[p].s<=(double)max(tr[tr[p].ls].s,tr[tr[p].rs].s)) return 1;
if((double)tr[p].sd<=1.0*alpha*tr[p].s) return 1;
return 0;
}
int dfn[N],tot;
void unfold(int p){//取出所有点
// cerr<<"unfold:"<<p<<endl;
if(!p) return ;
unfold(tr[p].ls);
if(tr[p].dt) dfn[tot++]=p;
unfold(tr[p].rs);
return ;
}
int build(int l,int r){//重构
// cerr<<"build:"<<l<<" "<<r<<endl;
if(l>=r) return 0;
int mid=(l+r)>>1;
int p=dfn[mid];
tr[p].ls=build(l,mid);
tr[p].rs=build(mid+1,r);
update(p);
return p;
}
void rebuild(int &p){
// cerr<<"rebuild:"<<p<<endl;
tot=0;
unfold(p);
p=build(0,tot);
// cerr<<"after rebuild:"<<p<<endl;
return ;
}
void insert(int &p,int v){//插入数
// cerr<<"insert:"<<p<<" "<<v<<endl;
if(!p){
p=++cnt;
if(!rt) rt=1;
tr[p].v=v;
tr[p].ls=tr[p].rs=0;
tr[p].dt=tr[p].s=tr[p].sd=tr[p].sz=1;
return ;
}
if(tr[p].v==v)
++tr[p].dt;
else if(tr[p].v<v) insert(tr[p].rs,v);
else insert(tr[p].ls,v);
update(p);
if(ifrub(p)) rebuild(p);
return ;
}
void delet(int &p,int k){
if(!p) return ;
if(tr[p].v==k){
if(tr[p].dt)
--tr[p].dt;
}
else if(tr[p].v<k)delet(tr[p].rs,k);
else delet(tr[p].ls,k);
update(p);
if(ifrub(p)) rebuild(p);
return ;
}
int rnk(int p,int v){
if(!p) return 1;
if(tr[p].v==v && tr[p].dt) return tr[tr[p].ls].sz+tr[p].dt+1;
if(tr[p].v>v) return rnk(tr[p].ls,v);
return tr[tr[p].ls].sz+tr[p].dt+rnk(tr[p].rs,v);
}
int valu(int p,int k){
if(!p) return 0;
if(tr[tr[p].ls].sz<k && k<=tr[tr[p].ls].sz+tr[p].dt)
return tr[p].v;
if(tr[tr[p].ls].sz+tr[p].dt<k)
return valu(tr[p].rs,k-tr[tr[p].ls].sz-tr[p].dt);
return valu(tr[p].ls,k);
}
int main(){
// freopen("P3369_3.in","r",stdin);
n=read();
int opt,x;
while(n--){
opt=read(),x=read();
// cerr<<"opt:"<<opt<<" "<<x<<endl;
// cerr<<"rt:"<<rt<<endl;
if(opt==1) insert(rt,x);
else if(opt==2) delet(rt,x);
else if(opt==3) printf("%d\n",rnk(rt,x-1));
else if(opt==4) printf("%d\n",valu(rt,x));
else if(opt==5) printf("%d\n",valu(rt,rnk(rt,x-1)-1));
else if(opt==6) printf("%d\n",valu(rt,rnk(rt,x)));
}
return 0;
}
LCT/Link Cut Tree/动态树
思想
-
对原树/森林建立一个辅助树/森林,对原树实链剖分,对于每一条实链用一个 \(\mathtt{Splay}\) 维护其在原树上的深度顺序(即中序遍历得原深度顺序),若干棵 \(\mathtt{Splay}\) 以虚边连接构成整个图。
-
每一个节点都记录它的父亲节点,但每一个父亲节点只能记录一个儿子。所有这样的双向关系都是实边,其他儿子→父亲的单向关系是虚边。
-
相当于,对于一棵原树投射到辅助树上,所有的边都先是虚边,然后在有需要的时候变成实边。
-
其他询问信息的维护基本通过正常 \(pushup\)、\(pushdown\) 来维护。
函数简介
-
\(\mathtt{Splay}\) 所需函数:\(Splay(x)+rotate(x)\)
-
其他函数:\(pushup(x)+pushdown(x)\),\(reverse(x)\)(实现区间翻转)
-
\(Access(x)\):将 \(x\) 到原树根节点的这条链变成实链。
也就是将 \(x\) 到根节点这一路上的点加入同一棵 \(\mathtt{Splay}\)。
从 \(x\) 出发,先把 \(x\) 的儿子断了,再跳到它的父亲。每次把 \(fa_x\) 换到 \(\mathtt{Splay}\) 的根节点上,再把 \(rs_{fa_x}\) 设为 \(x\)。最后更新节点。
Access
void Access(int x){
for(int i=0;x;i=x,x=tr[x].fa){
Splay(x);
rs(x)=i;
pushup(x);
}
return ;
}
- \(make\_root(x)\):将 \(x\) 变成原树根节点。
先打通 \(x\) 到现根节点的实链,然后将 \(x\) 换到 \(\mathtt{Splay}\) 根节点。
由于根节点变了,所以这一路上的所有点的深度都会变。比如 \(x→root\) 深度 \(4\sim 1\),现在变为 \(root→x\) 深度 \(4\sim 1\)。投射到 \(\mathtt{Splay}\) 上就是区间翻转。
make_root
void make_root(int x){
Access(x);
Splay(x);
reverse(x);
return ;
}
- \(split(x,y)\):取出 \(x→y\) 的这条链。
先把 \(x\) 变为原树根节点,再打通 \(y\) 到 \(x\) 的实链,然后在 \(\mathtt{Splay}\) 上把 \(y\) 转到根节点。
Split
void Split(int x,int y){
make_root(x);
Access(y);
Splay(y);
return ;
}
- \(Find(x)\):找到 \(x\) 所在原树根节点。
先打通 \(x\) 到根节点的实链,再把 \(x\) 转到 \(\mathtt{Splay}\) 根节点。由于原树根节点的深度是整个 \(\mathtt{Splay}\) 里最小的,一直找 \(ls_x\) 就行了。
最后再把 \(Splay\) 转回去。
可以用于判断两点是否连通。
Find
int Find(int x){
Access(x);
Splay(x);
pushdown(x);
while(ls(x)){
x=ls(x);
pushdown(x);
}
Splay(x);
return x;
}
- \(Link(x,y)\):增加 \(x\) 和 \(y\) 之间的边。
把 \(x\) 变成原树根节点,然后让 \(y\) 成为它的父亲。
有时需要判断两点是否已连通/是否直接有边相连。
Link
void Link(int x,int y){
make_root(x);
tr[x].fa=y;
return ;
}
- \(Cut(x,y)\):删掉 \(x\) 和 \(y\) 之间的边。
取出 \(x→y\) 的链,若相连,\(y\) 必定是 \(x\) 的父亲,所以判断一下是否满足父子关系以及 \(x\) 节点没有 \(rs\) 再直接清空即可。
有时需要判断两点是否直接有边相连,再删除。
Cut
void Cut(int x,int y){
Split(x,y);
if(ls(y)==x && !rs(x))
ls(y)=tr[x].fa=0;
return ;
}
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
const int N=1e5+10;
int n,m;
struct memr{
int son[2],fr;
int sum,val;
int tg;
}tr[N];
#define isr(i) (tr[tr[i].fr].son[0]!=i && tr[tr[i].fr].son[1]!=i)
#define Get(i) (i==tr[tr[i].fr].son[1])
void rev(int p){
swap(tr[p].son[0],tr[p].son[1]);
tr[p].tg^=1;
return ;
}
void pushup(int p){
tr[p].sum=tr[tr[p].son[0]].sum^tr[p].val^tr[tr[p].son[1]].sum;
return ;
}
void pushdown(int p){
if(tr[p].tg){
rev(tr[p].son[0]);
rev(tr[p].son[1]);
tr[p].tg=0;
}
return ;
}
void update(int p){
if(!isr(p)) update(tr[p].fr);
pushdown(p);
return ;
}
void rotate(int x){
// printf("rotate %d\n",x);
int y=tr[x].fr;
int z=tr[y].fr,f=Get(x);
if(!isr(y)) tr[z].son[tr[z].son[1]==y]=x;
tr[x].fr=z;
tr[y].son[f]=tr[x].son[f^1];
tr[tr[x].son[f^1]].fr=y;
tr[x].son[f^1]=y;
tr[y].fr=x;
pushup(y),pushup(x);
return ;
}
void Splay(int x){
// printf("Splay %d\n",x);
update(x);
for(int i;i=tr[x].fr,!isr(x);rotate(x))
if(!isr(i))
rotate((Get(x)==Get(i))?i:x);
return ;
}
void Access(int p){
// printf("Access %d\n",p);
int x,t=p;
for(x=0;p;x=p,p=tr[p].fr){
Splay(p);
tr[p].son[1]=x;
pushup(p);
}
Splay(t);
return ;
}
void make_root(int p){
Access(p);
rev(p);
return ;
}
int find(int p){
Access(p);
while(tr[p].son[0])
pushdown(p),p=tr[p].son[0];
Splay(p);
return p;
}
void Link(int x,int y){
make_root(x);
if(find(y)!=x)
tr[x].fr=y;
return ;
}
void Cut(int x,int y){
make_root(x);
if(find(y)==x && tr[x].son[1]==y && !tr[y].son[0]){
tr[y].fr=tr[x].son[1]=0;
pushup(x);
}
return ;
}
void fix(int x,int v){
Splay(x);
tr[x].val=v;
pushup(x);
return ;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;++i)
tr[i].val=read();
int opt,x,y;
while(m--){
opt=read(),x=read(),y=read();
if(!opt){
make_root(x);
Access(y);
printf("%d\n",tr[y].sum);
}
else if(opt==1)
Link(x,y);
else if(opt==2)
Cut(x,y);
else fix(x,y);
}
return 0;
}
K-D Tree
-
一种可以用来维护多维空间中的点的数据结构。
-
基于平衡树的思想,每一层下分时轮流按照 \(k\) 个维度来进行区间的划分。
如果需要支持插入操作,则需要使用替罪羊树的算法来维护树的平衡。 -
一种好用的函数:
nth_element(a+1,a+x+1,a+n+1);
具体效果为将第 \(x\) 小的数放到第 \(x\) 位,然后其后的数字都大于它,前面的数字都小于它,但不一定是顺序的。
-
题目:P4148 简单题(可插入的 KD Tree)
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
#define alpha 0.73
const int N=2e5+5;
int n;
int lastans=0,rt;
int cnt=0;
struct Points{
int x[2],v;
}rp[N];
struct memr{
int l[2],r[2];
int sum,siz,son[2];
Points val;
#define ls(p) tr[p].son[0]
#define rs(p) tr[p].son[1]
}tr[N];
int rec[N],tot=0;
int pt=0;
int now;
int _New(){
return tot?rec[tot--]:++cnt;
}
void pushup(int p){
for(int i=0;i<2;++i){
tr[p].l[i]=tr[p].r[i]=tr[p].val.x[i];
if(ls(p)){
tr[p].l[i]=min(tr[p].l[i],tr[ls(p)].l[i]);
tr[p].r[i]=max(tr[p].r[i],tr[ls(p)].r[i]);
}
if(rs(p)){
tr[p].l[i]=min(tr[p].l[i],tr[rs(p)].l[i]);
tr[p].r[i]=max(tr[p].r[i],tr[rs(p)].r[i]);
}
}
tr[p].sum=tr[ls(p)].sum+tr[rs(p)].sum+tr[p].val.v;
tr[p].siz=tr[ls(p)].siz+tr[rs(p)].siz+1;
return ;
}
bool cmp(Points a,Points b){
return a.x[now]<b.x[now];
}
void unfold(int p){
if(!p) return ;
unfold(ls(p));
rp[++pt]=tr[p].val;
rec[++tot]=p;
unfold(rs(p));
return ;
}
int rebuild(int l,int r,int i){
if(l>r) return 0;
int p=_New();
int mid=(l+r)>>1;
now=i;
nth_element(rp+l,rp+mid,rp+r+1,cmp);
tr[p].val=rp[mid];
ls(p)=rebuild(l,mid-1,i^1);
rs(p)=rebuild(mid+1,r,i^1);
pushup(p);
return p;
}
void check(int &p,int i){
if(max(tr[rs(p)].siz,tr[ls(p)].siz)>alpha*tr[p].siz){
pt=0;
unfold(p);
p=rebuild(1,tr[p].siz,i);
}
return ;
}
void insert(int &p,Points val,int i){
if(!p){
p=_New();
tr[p].val=val;
ls(p)=rs(p)=0;
pushup(p);
return ;
}
if(val.x[i]<=tr[p].val.x[i])
insert(ls(p),val,i^1);
else insert(rs(p),val,i^1);
pushup(p);
check(p,i);
return ;
}
int ask(int p,int xl,int yl,int xr,int yr){
if(!p) return 0;
if(xl>tr[p].r[0] || xr<tr[p].l[0]) return 0;
if(yl>tr[p].r[1] || yr<tr[p].l[1]) return 0;
if(xl<=tr[p].l[0] && yl<=tr[p].l[1] && xr>=tr[p].r[0] && yr>=tr[p].r[1])
return tr[p].sum;
int ans=0;
if(xl<=tr[p].val.x[0] && xr>=tr[p].val.x[0] && yl<=tr[p].val.x[1] && yr>=tr[p].val.x[1])
ans+=tr[p].val.v;
ans+=ask(ls(p),xl,yl,xr,yr);
ans+=ask(rs(p),xl,yl,xr,yr);
return ans;
}
int main(){
n=read();
int opt,a,b,c,d;
while(opt=read()){
if(opt==3) break;
a=read()^lastans,b=read()^lastans,c=read()^lastans;
if(opt==1){
Points dx;
dx.x[0]=a,dx.x[1]=b,dx.v=c;
insert(rt,dx,0);
}
else{
d=read()^lastans;
if(a>c || b>d) lastans=0;
else lastans=ask(rt,a,b,c,d);
printf("%d\n",lastans);
}
}
return 0;
}