树链剖分
树链剖分
前言
重剖:从去年省选鸽到现在,其实更准确的是从初二下学期第一次学,一直鸽到现在。
笑点解析:重剖:我叫 tree_pou_heavy(重的树剖)
简介
树链剖分就是把树按照某种规定剖成若干条链,便于维护修改和查询。目前我知道的有重链剖分、长链剖分、实链剖分(又称 LCT)。
重链剖分
重链剖分可以维护树上链加、链查、子树加、子树查。基本上有链操作的都可以考虑下重剖(和长剖?)。
每个节点,子树大小(指节点个数)最大的儿子,称为重儿子,其他儿子叫做轻儿子。每个节点和其重儿子连边叫做重边,和其轻儿子连边叫做轻边。把轻边断开,树就被分成了若干条(最多 \(O(n)\) 条)链(一个节点也算一条链)。
我们按照先遍历重儿子,再遍历轻儿子的访问顺序钦定 dfs 序,这样可以保证一条链的 dfs 序是连续的。
我们使用数据结构维护每条链的信息,常见的有线段树维护。
点 \(u,v\) 之间的链,可以划分成若干条树链,我们对每条树链分别进行区间加、区间查询操作即可。对每个节点记录一个 \(top_u\) 表示点 \(u\) 所在的链,深度最小的点是什么。方便跳链。
如果对每条链单独开一棵线段树维护,常数应该会较小,但是如果题目还有子树操作,就必须维护全局的线段树了。
证明点 \(u,v\) 之间有 \(O(\log n)\) 条树链:我们考虑 \(u,lca\) 之间有多少树链。也就是数 \(u,lca\) 之间有多少条轻边。每经过一条轻边从 \(x\) 到达 \(fa_x\),说明 \(fa_x\) 一定有一个重儿子 \(y\) 满足 \(siz_y \ge sizx\),因此 \(siz_{fa_x} \ge 2siz_x\),子树大小会翻倍,而最多翻 \(\log n\) 倍。因此这样的轻边只有 \(O(\log n)\) 条。证毕。
加上线段树复杂度,时间复杂度是 \(O(n \log^2 n)\)。重剖常数是较小的,因为你一般跳不完 \(\log n\) 条链。只是线段树常数较大。但是我们有可爱的 zkw 小常数线段树。
综上,重剖预处理 \(O(n)\),单次操作 \(O(\log)\) 乘上每条链操作时间复杂度。优点是比较板,需要上树剖的题目一般维护的操作比较雷同(当然你需要先推出操作)。缺点是码量较大。
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace tree_pou_heavy {
constexpr int N=1e5+7;
int n,m,rt,mod;
int add(int a,int b) { return a+b>=mod ? a+b-mod : a+b; }
void _add(int &a,int b) { a=add(a,b); }
void _max(int &a,int b) { a=max(a,b); }
int a[N];
int u,v;
struct edge {
int to,ne;
}e[N<<1];
int head[N],e_cnt;
void addedge(int u,int v) { e[++e_cnt] = {v, head[u]}; head[u]=e_cnt; }
struct tree {
int fa[N],siz[N],gson[N],dep[N];
int dfn[N],eddfn[N],cnt,idfn[N];
int top[N];
void dfs(int u,int f) {
fa[u]=f;
dep[u]=dep[f]+1;
siz[u]=1;
for(int i=head[u]; i; i=e[i].ne) {
int v=e[i].to;
if(v==f) continue;
dfs(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[gson[u]]) gson[u]=v;
}
}
void dfs(int u) {
dfn[u]=eddfn[u]=++cnt;
idfn[cnt]=u;
if(gson[u]) top[gson[u]]=top[u], dfs(gson[u]);
for(int i=head[u]; i; i=e[i].ne) {
int v=e[i].to;
if(v==fa[u] || v==gson[u]) continue;
top[v]=v, dfs(v);
}
eddfn[u]=cnt;
}
int tr[N<<2],tag[N<<2];
int p;
void build() {
p=1;
while(p-2<n) p<<=1;
rep(i,1,n) tr[p+i]=a[idfn[i]];
per(i,p-1,1) tr[i]=add(tr[i<<1],tr[i<<1|1]);
}
void init() {
dfs(rt,0);
top[rt]=rt;
dfs(rt);
build();
}
void maketag(int u,int x,int siz) { _add(tr[u],1ll*x*siz%mod), _add(tag[u],x); }
void pushup(int u,int siz) { tr[u]=add(add(tr[u<<1],tr[u<<1|1]),1ll*siz*tag[u]%mod); }
void _update(int l,int r,int x) {
l=p+l-1, r=p+r+1;
int siz=1;
while(l^r^1) {
if((l&1)^1) maketag(l^1,x,siz);
if(r&1) maketag(r^1,x,siz);
l>>=1, r>>=1, siz<<=1;
pushup(l,siz), pushup(r,siz);
}
for(l>>=1, siz<<=1; l; l>>=1, siz<<=1) pushup(l,siz);
}
int _query(int l,int r) {
l=p+l-1, r=p+r+1;
int sizl=0,sizr=0,siz=1;
int s=0;
while(l^r^1) {
if((l&1)^1) _add(s,tr[l^1]), sizl+=siz;
if(r&1) _add(s,tr[r^1]), sizr+=siz;
l>>=1, r>>=1, siz<<=1;
_add(s,1ll*tag[l]*sizl%mod), _add(s,1ll*tag[r]*sizr%mod);
}
for(l>>=1, sizl+=sizr; l; l>>=1) _add(s,1ll*tag[l]*sizl%mod);
return s;
}
void update(int u,int v,int x) {
while(top[u]^top[v]) {
if(dep[top[u]]<dep[top[v]]) swap(u,v);
_update(dfn[top[u]],dfn[u],x), u=fa[top[u]];
}
if(dfn[u]>dfn[v]) swap(u,v);
_update(dfn[u],dfn[v],x);
}
void update(int u,int x) {
_update(dfn[u],eddfn[u],x);
}
int query(int u,int v) {
int s=0;
while(top[u]^top[v]) {
if(dep[top[u]]<dep[top[v]]) swap(u,v);
_add(s,_query(dfn[top[u]],dfn[u])), u=fa[top[u]];
}
if(dfn[u]>dfn[v]) swap(u,v);
_add(s,_query(dfn[u],dfn[v]));
return s;
}
int query(int u) {
return _query(dfn[u],eddfn[u]);
}
}T;
int op,x;
void main() {
sf("%d%d%d%d",&n,&m,&rt,&mod);
rep(i,1,n) sf("%d",&a[i]), a[i]%=mod;
rep(i,1,n-1) sf("%d%d",&u,&v), addedge(u,v), addedge(v,u);
T.init();
rep(i,1,m) {
sf("%d",&op);
if(op==1) sf("%d%d%d",&u,&v,&x), T.update(u,v,x);
else if(op==2) sf("%d%d",&u,&v), pf("%d\n",T.query(u,v));
else if(op==3) sf("%d%d",&u,&x), T.update(u,x);
else sf("%d",&u), pf("%d\n",T.query(u));
}
}
}
int main() {
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("my.out","w",stdout);
#endif
tree_pou_heavy :: main();
}
本文来自博客园,作者:liyixin,转载请注明原文链接:https://www.cnblogs.com/liyixin0514/p/18570528