【学习笔记】全局平衡二叉树
全局平衡二叉树
全局平衡二叉树的样子(出自 2007 年的一篇论文)
全局平衡二叉树可以理解为一个静态的 LCT。首先将这棵树重链剖分,可以得到 个重链。然后我们设 表示该点的所有轻子树大小加一( 子树大小减去重儿子子树大小)。然后对于我们每一条重链,我们要建出其 Auxiliary Tree(即 LCT 上的 splay)。这个 Auxiliary Tree 应该是一个 BST,并且其权值为 。具体建法:
- 对于每一个重链的区间 ( 为 的祖先):
- 暴力找到它的带权中点 (设 是 的前缀和,则找到位置 满足 的 ),于是 为这个区间的根。
- 分别计算左儿子区间 和右儿子区间 。这样我们一定有左儿子为祖先,右儿子为子孙。
- 对于每个 Auxiliary Tree 的根 ,它向它的父节点在全局平衡二叉树上的点连一条虚边(Path Parent 边)。
然后就建好了!很简单!
这个全局平衡二叉树的高度是 的。意味着有很多事情能单 log 直接做到。
QTREE1 点修链查
考虑每个 维护其 Auxiliary Tree 上的子树最大值 。
修改操作:从 开始向上爬。如果爬的是 Auxiliary Tree 内部的边,要更新父亲的 。如果爬的是 Parent Path 就不用更新了。复杂度 。
查询操作:
- 从 开始向上爬。边爬边用这个点的左儿子的 值和自己的值去更新答案,直到爬到了 所在重链。
- 对于任意一个同重链祖先链询问 ( 是 的祖先),相当于在一个 Auxiliary Tree 上做一个(深度)区间询问。Auxiliary Tree 本来就是点有关深度的一个 BST,直接像线段树那样做就行了。
复杂度 。
这玩意儿写起来和树剖没长多少(就是建树稍微长了一点),常数很小,还比树剖少个 log,很有趣。
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<pii> vp;
typedef unsigned long long ull;
long long read() {
long long res=0, w=1; char c=getchar();
while(!isdigit(c)) {if(c=='-') w=-1; c=getchar();}
while(isdigit(c)) {res=res*10+c-48, c=getchar();}
return res*w;
}
const int N=1e5+9;
int n,w[N],sz[N],d[N],son[N],id[N],fa[N],top[N],lsz[N];
vector<pair<int,pii> > e[N];
vp t[N];
char op[N];
void dfs1(int u) {
d[u]=d[fa[u]]+1; sz[u]=1;
for(auto ed:e[u]) if(ed.fi!=fa[u]) {
int v=ed.fi; w[v]=ed.se.fi; id[ed.se.se]=v; fa[v]=u;
dfs1(v); sz[u]+=sz[v]; if(sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int tp,int ss) {
top[u]=tp; lsz[u]=sz[u]-sz[son[u]]; t[tp].emplace_back(pii(u,ss+lsz[u]));
for(auto v:e[u]) if(v.fi!=son[u]&&v.fi!=fa[u]) dfs2(v.fi,v.fi,0);
if(son[u]) dfs2(son[u],tp,ss+lsz[u]);
}
namespace GBST {
int ls[N],rs[N],mx[N],af[N],pf[N],mind[N],maxd[N],rt[N];
int build(int tp,int l,int r) {
if(l>r) return 0;
if(l==r) {
int x=t[tp][l].fi; mx[x]=w[x], mind[x]=d[x], maxd[x]=d[x];
return x;
}
int sum=(l?t[tp][l-1].se:0)+t[tp][r].se>>1;
rep(k,l,r) if(t[tp][k].se>sum) {
int x=t[tp][k].fi;
af[ls[x]=build(tp,l,k-1)]=x, af[rs[x]=build(tp,k+1,r)]=x;
mx[x]=max(w[x],max(mx[ls[x]],mx[rs[x]]));
if(ls[x]) mind[x]=min(d[x],mind[ls[x]]); else mind[x]=d[x];
if(rs[x]) maxd[x]=max(d[x],maxd[rs[x]]); else maxd[x]=d[x];
return x;
}
return assert(0), -1;
}
void init() {
rep(i,1,n) {
if(i==top[i]) rt[i]=build(i,0,t[i].size()-1), pf[rt[i]]=fa[i];
}
}
void mdf(int x,int y) {
w[x]=y, mx[x]=max(max(mx[ls[x]],mx[rs[x]]),w[x]);
while(x) {
if(af[x]) x=af[x], mx[x]=max(max(mx[ls[x]],mx[rs[x]]),w[x]);
else x=pf[x];
}
}
int qry(int u,int l,int r,int res=0) {
if(l>r) return 0; if(!u) return 0;
if(l<=d[u]&&d[u]<=r) res=max(res,w[u]);
res=max(res,d[u]<=r&&mind[u]>=l?mx[ls[u]]:qry(ls[u],l,min(d[u]-1,r)));
res=max(res,d[u]>=l&&maxd[u]<=r?mx[rs[u]]:qry(rs[u],max(l,d[u]+1),r));
return res;
}
int query(int u,int v,int ans=0) {
while(top[u]!=top[v]) {
if(d[top[u]]<d[top[v]]) swap(u,v);
for(int yu=u;;u=af[u]) {
if(d[u]<=d[yu]) ans=max(ans,max(w[u],mx[ls[u]]));
if(!af[u]) break;
} u=pf[u];
} if(d[u]<d[v]) swap(u,v);
return max(ans,qry(rt[top[u]],d[v]+1,d[u]));
}
}
signed main() {
n=read();
rep(i,2,n) {
int u=read(), v=read(), w=read();
e[u].emplace_back(make_pair(v,pii(w,i-1)));
e[v].emplace_back(make_pair(u,pii(w,i-1)));
}
dfs1(1); dfs2(1,1,0); GBST::init();
while(1) {
scanf("%s",op); int x,y;
if(op[0]=='D') break;
else if(op[0]=='C') x=read(), y=read(), GBST::mdf(id[x],y);
else x=read(), y=read(), printf("%d\n",GBST::query(x,y));
}
return 0;
}
几乎没怎么硬卡常,让这个解排到了洛谷最优解首页。如果把前面 Build 所用的 vector 换成其他结构,加一个更好的快读,那么可以再快一些。不过这样卡常就很无趣了。
本文作者:LarsWerner
本文链接:https://www.cnblogs.com/TetrisCandy/p/16574972.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步