1.树剖的思想
树剖本质上是维护一些树上的操作,把树中任意一段路径划分成不超过2log2n段连续区间
然后就上一些数据结构(线段树,树状数组,平衡树......)维护区间即可
2.一些关于树剖的定义
-
重/轻儿子:对于任意一个非叶节点的节点,都会有一个重儿子,其定义为其儿子节点中所在节点最多子树的子节点,其余为轻儿子。
-
重/轻边:由一个节点连向它的重儿子的边叫做重边,其余的叫做轻边。
-
重链:指由重链构成的极大路径。
(如图,红色点为重儿子,白色点为轻儿子,红色边为重边,白色边为轻边)
观察上图,我们可以发现一个轻儿子都是一个重链的顶点,且每一个重链的dfs序都是连续的。(优先遍历重儿子)
3.如何实现
上文所述:树中任意一段路径可以划分成不超过log2n段连续区间
如何实现?
考虑类似LCA式去求解
从这条路径的两个端点向上爬,每一次跳到它所在重链的父节点上,直到跳到这两个点的最近公共重链上为止,这样就很好地完成了上面的问题。
4.代码实现
先贴一下代码,在注释里详细说吧
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100010,M=N*2;
int n,m;
int w[N],h[N],e[M],ne[M],idx;//邻接表
int id[N],nw[N],cnt;//id表示dfs序,nw表示点权,cnt表示dfs序当前分配到的位置
int dep[N],sz[N],top[N],son[N],fa[N];//dep表示每个点的深度,sz表示子树大小,top表示每个点所在重链的顶点,son表示重儿子,fa表示父节点
struct node//线段树
{
int l,r;
int add,sum;
}tr[N*4];
void pushup(int u)
{
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)
{
node &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
if(root.add)
{
left.add+=root.add;left.sum+=root.add*(left.r-left.l+1);
right.add+=root.add;right.sum+=root.add*(right.r-right.l+1);
root.add=0;
}
}
void build(int u,int l,int r)
{
tr[u]={l,r,0,nw[r]};
if(l==r)return;
int mid=l+r>>1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
pushup(u);
}
void update(int u,int l,int r,int d)
{
if(tr[u].l>=l&&tr[u].r<=r)
{
tr[u].sum+=(tr[u].r-tr[u].l+1)*d;
tr[u].add+=d;
return;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid)update(u<<1,l,r,d);
if(r>mid)update(u<<1|1,l,r,d);
pushup(u);
}
int query(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum;
pushdown(u);
int res=0;
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid)res+=query(u<<1,l,r);
if(r>mid)res+=query(u<<1|1,l,r);
return res;
}
void add(int a,int b)//建一条a->b的的边
{
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs1(int u,int father,int depth)//第一遍dfs,维护dep,fa,sz,son的信息
{
dep[u]=depth;fa[u]=father;sz[u]=1;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j==father)continue;
dfs1(j,u,depth+1);
sz[u]+=sz[j];
if(sz[son[u]]<sz[j])son[u]=j;
}
}
void dfs2(int u,int t)//第二遍dfs,维护id,nw,top的信息
{
id[u]=++cnt;nw[cnt]=w[u];top[u]=t;
if(!son[u])return;
dfs2(son[u],t);
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j==fa[u]||j==son[u])continue;
dfs2(j,j);
}
}
void update_path(int u,int v,int k)//修改路径
{
while(top[u]!=top[v])//向上跳的过程
{
if(dep[top[u]]<dep[top[v]])swap(u,v);
update(1,id[top[u]],id[u],k);
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
update(1,id[v],id[u],k);
}
void update_tree(int u,int k)//修改子树
{
update(1,id[u],id[u]+sz[u]-1,k);
}
int query_path(int u,int v)//查询路径和
{
int res=0;
while(top[u]!=top[v])//同上
{
if(dep[top[u]]<dep[top[v]])swap(u,v);
res+=query(1,id[top[u]],id[u]);
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
res+=query(1,id[v],id[u]);
return res;
}
int query_tree(int u)//查询子树和
{
return query(1,id[u],id[u]+sz[u]-1);
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>w[i];
memset(h,-1,sizeof h);
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
add(a,b);add(b,a);
}
dfs1(1,-1,1);
dfs2(1,1);
build(1,1,n);//建树
cin>>m;
while(m--)
{
int op,u,v,k;
cin>>op>>u;
if(op==1)
{
cin>>v>>k;
update_path(u,v,k);
}
else if(op==2)
{
cin>>k;
update_tree(u,k);
}
else if(op==3)
{
cin>>v;
cout<<query_path(u,v)<<endl;
}
else
cout<<query_tree(u)<<endl;
}
return 0;
}