题解 LGP4211【[LNOI2014]LCA】/【模板】全局平衡二叉树
ref: P4211 [LNOI2014]LCA | 全局平衡二叉树 - 洛谷专栏 (luogu.com.cn)
题目描述
一棵树,多次给定 \(l,r,z\) 询问 \(\sum_{l\leq i\leq r}dep_{lca(i,z)}\),允许离线,\(n\leq 50000\)。
solution
转换:这个 \(dep_u\) 的定义为 \(u\) 到根节点的点数。如果我们对于每个 \(l\leq i\leq r\),都给 \(1\leftrightarrow i\) 这条链加一,那么直接询问 \(1\leftrightarrow z\) 上的点权之和就可以了。
考虑到 \([l,r]\) 的答案可以差分成 \([1,l-1]\) 和 \([1,r]\) 的答案,直接扫描线即可。
现在的问题变成了树链加,树链求和。
全局平衡二叉树
全局平衡二叉树可以解决链修改、链查询的问题,复杂度为单次 \(O(\log n)\)。运用了 LCT 的思想。
以下是全局平衡二叉树的构建。对于一棵树,我们首先给它做重链剖分。将每条重链拎出来单独建一棵二叉树(注意不能建 leafy 的),这棵二叉树的每个点的权值是它的轻子树大小和加上它自己(\(siz_u-siz_{son_u}\)),然后每一层都取带权中心;这个二叉树的中序遍历恰是深度从浅到深的重链遍历。重链头的父亲是它在原树上的父亲,且这个父亲不记录这个儿子。
接下的任务是修改和查询。我们可以先考虑一个点到根的路径怎么表示。
第一步是从这个点暴力跳全局平衡二叉树的父亲。观察到原树上跳轻边,因为重链剖分,所以只有 \(O(\log n)\) 条轻边;除了轻边外就是二叉树里的边,二叉树的边,每跳一次总的轻子树大小就翻倍(考虑总权重,带权中心),所以一共也就 \(O(\log n)\) 条边。所以这一步暴力跳父亲一共 \(O(\log n)\) 次。
第二步,有懒标记的从上向下下传。标记永久化的不管。
第三步,从下向上,对于询问点和从轻边跳上去的点(统称 \(p\)),我们要加比它们深度小的所有点。那么我们从 \(p\) 点逐个父亲跳上去的时候,如果当前点的左儿子不是上一个点(这时说明当前点可能为询问点,或者刚刚跳了轻边,或者上一个点是右儿子,说明当前点和它左子树比 \(p\) 在原树的深度都小,都要加,在当前点这个单点加一次,在它的左子树打子树加的懒标记。(或者标记永久化也行,这样这个标记贡献要继续上传,跳出轻边时记得删掉)这样就能处理完所有的。
我们相当于把询问的链拆成若干单点和它们中一些点的左子树,而这个子树加天然带有懒标记/标记永久化的结构,这样就省去一个线段树结构。(实际上可以认为这个左子树是不动的 splay 树,整个是一个 splay 森林——所以它本质上是不动的 LCT)
全局平衡二叉树可以优化动态 DP。
若是任意两个点之间的链,可能在跳到同一条重链之后有比较麻烦的讨论。可能是发现两个点在同一重链后,首先判断原树 LCA(是深度较浅者),然后两个点同时往上跳,深度小的去找比自己深度大的点打标记,深度大的去找比自己深度小的点打标记,直到在二叉树上碰面,在二叉树上 LCA 打单点标记。
若有子树操作,据说可以类似 LCT 维护轻子树信息,不确定是不是真的,但其实不如 dfn 序。
Codes
全局平衡二叉树
void dfs(int u){
siz[u]=1;
for(int v:g[u]) if(v!=fa[u]){
dfs(v),siz[u]+=siz[v];
if(siz[son[u]]<siz[v]) son[u]=v;
}
}
int cf[1<<16],ch[1<<16][2],cs[1<<16];//LCT father
int chain[1<<16];
int cbuild(int l,int r){
if(l==r) return cs[chain[l]]=1,chain[l];
int s=0,now=0,mid;
for(int i=l;i<=r;i++) s+=siz[chain[i]]-siz[son[chain[i]]];
for(int i=l;i<=r;i++){
now+=siz[chain[i]]-siz[son[chain[i]]];
if(now<<1>=s){mid=i;break;}
}
int x=chain[mid]; cs[x]=r-l+1;
if(l<mid) cf[ch[x][0]=cbuild(l,mid-1)]=x;
if(mid<r) cf[ch[x][1]=cbuild(mid+1,r)]=x;
return x;
}
int build(int u){
for(int x=u;x;x=son[x]) for(int v:g[x]) if(v!=fa[x]&&v!=son[x]) cf[build(v)]=x;
int cnt=0;
for(int x=u;x;x=son[x]) chain[++cnt]=x;
return cbuild(1,cnt);
}
LL ans[1<<16],tag[1<<16],his[1<<16];
void maintain(int p){if(p) ans[p]=ans[ch[p][0]]+ans[ch[p][1]]+his[p];}
void spread(int p,int k){if(p) ans[p]+=cs[p]*k,tag[p]+=k,his[p]+=k;}
void pushdown(int p){if(p&&tag[p]) spread(ch[p][0],tag[p]),spread(ch[p][1],tag[p]),tag[p]=0;}
void modify(int p){
int cnt=0;
for(int u=p;u;u=cf[u]) chain[++cnt]=u;
for(int i=cnt;i>=1;i--) pushdown(chain[i]);
for(int i=1;i<=cnt;i++){
int u=chain[i];
if(i==1||ch[u][0]!=chain[i-1]){
his[u]++;
if(ch[u][0]) spread(ch[u][0],1);
}
maintain(u);
}
}
LL query(int p){
int cnt=0;
for(int u=p;u;u=cf[u]) chain[++cnt]=u;
for(int i=cnt;i>=1;i--) pushdown(chain[i]);
LL res=0;
for(int i=1;i<=cnt;i++){
int u=chain[i];
if(i==1||ch[u][0]!=chain[i-1]){
res+=his[u];
if(ch[u][0]) res+=ans[ch[u][0]];
}
}
return res;
}
树剖线段树
#include <cstdio>
#include <vector>
#include <cstring>
#include <cassert>
#include <algorithm>
#include <functional>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
const int P=201314;
void red(LL&x){x=(x%P+P)%P;}
template<class T> struct Ans{
int len; T sum;
Ans(int len=1,T sum=0):len(len),sum(sum%P){}
Ans operator+(Ans b){return Ans(len+b.len,sum+b.sum);}
Ans operator+=(int k){return red(sum+=1ll*k*len),*this;}
};
template<int N,class T,class A> struct segtree{
T tag[N<<2]; A ans[N<<2];
segtree(){build();}
void build(int p=1,int l=1,int r=N){
if(l==r) return ; int mid=(l+r)>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
ans[p]=ans[p<<1]+ans[p<<1|1];
}
void add(T k,int p){tag[p]+=k,ans[p]+=k;}
void pushdown(int p){add(tag[p],p<<1),add(tag[p],p<<1|1),tag[p]=0;}
void modify(int L,int R,int k,int p=1,int l=1,int r=N){
if(L<=l&&r<=R) return add(k,p);
int mid=(l+r)>>1; pushdown(p);
if(L<=mid) modify(L,R,k,p<<1,l,mid);
if(mid<R) modify(L,R,k,p<<1|1,mid+1,r);
ans[p]=ans[p<<1]+ans[p<<1|1];
}
A query(int L,int R,int p=1,int l=1,int r=N){
if(L<=l&&r<=R) return ans[p];
int mid=(l+r)>>1; A res=A(0,0); pushdown(p);
if(L<=mid) res=query(L,R,p<<1,l,mid);
if(mid<R) res=res+query(L,R,p<<1|1,mid+1,r);
return res;
}
};
template<int N,int M,class T=int> struct graph{
int head[N+10],nxt[M*2+10],cnt;
struct edge{
int u,v; T w;
edge(int u=0,int v=0,T w=0):u(u),v(v),w(w){}
} e[M*2+10];
edge& operator[](int i){return e[i];}
graph(){memset(head,cnt=0,sizeof head);}
void add(int u,int v,int w=0){e[++cnt]=edge(u,v,w),nxt[cnt]=head[u],head[u]=cnt;}
void link(int u,int v,int w=0){add(u,v,w),add(v,u,w);}
};
template<int N,int M,class T=int> struct treecut:public graph<N,M,T>{
graph<N,M,T>&g=*this;
int fa[N+10],siz[N+10],son[N+10],dep[N+10],
dfn[N+10],top[N+10],rnk[N+10],cnt;
treecut():cnt(0){memset(son,siz[0]=0,sizeof son);}
void dfs(int u,int f=0){
dep[u]=dep[fa[u]=f]+1,siz[u]=1;
for(int i=g.head[u];i;i=g.nxt[i]){
int v=g[i].v; if(v==fa[u]) continue;
dfs(v,u),siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void cut(int u,int topf){
top[rnk[dfn[u]=++cnt]=u]=topf;
if(son[u]) cut(son[u],topf);
for(int i=g.head[u];i;i=g.nxt[i]){
int v=g[i].v; if(v==fa[u]||v==son[u]) continue;
cut(v,v);
}
}
void split(int u,int v,function<void(int,int)> op){
for(;top[u]!=top[v];u=fa[top[u]]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
op(dfn[top[u]],dfn[u]);
}
if(dep[u]<dep[v]) swap(u,v);
op(dfn[v],dfn[u]);
}
};
struct ask{
int pos,w,id,u;
ask(int pos=0,int w=0,int id=0,int u=0):pos(pos),w(w),id(id),u(u){}
bool operator<(ask b){return pos<b.pos;}
};
int n,m,cnt;
LL ret[50010];
ask q[100010];
treecut<50010,50010> g;
segtree<50010,int,Ans<LL>> t;
int main(){
// #ifdef LOCAL
// freopen("input.in","r",stdin);
// #endif
scanf("%d%*d",&n);
for(int i=2,f;i<=n;i++) scanf("%d",&f),g.link(i,f+1);
g.dfs(1),g.cut(1,1);
for(int l,r,z;~scanf("%d%d%d",&l,&r,&z);){
cnt++,z++,l++,r++;
if(l>1) q[++m]=ask(l-1,-1,cnt,z);
q[++m]=ask(r,1,cnt,z);
}
sort(q+1,q+m+1);
for(int i=1,now=1;i<=n;i++){
g.split(i,1,[&](int l,int r){
assert(1<=l&&l<=r&&r<=n);
// debug("modify(dfn)(%d,%d)\n",l,r);
t.modify(l,r,1);
});
for(;now<=m&&q[now].pos==i;now++){
g.split(q[now].u,1,[&](int l,int r){
assert(1<=l&&l<=r&&r<=n);
// debug("query(dfn)(%d,%d)\n",l,r);
red(ret[q[now].id]+=t.query(l,r).sum*q[now].w);
});
}
}
for(int i=1;i<=cnt;i++) printf("%lld\n",ret[i]);
return 0;
}
然而时代在变化,代码也越来越好看。。。可以参考 题解 GD230531C【眺望】 - caijianhong - 博客园 (cnblogs.com) 有个也许更好看的全局平衡二叉树。
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/solution-P4211.html