引入
如果在一棵树中,有操作:
1:把结点X的权值变成Y。
∵在树中,所以先用dfs序把树变成链。
2询问从点u到点v的路径上的节点的最大权值。
树上ST即可
3把某个节点 x 为根的子树中所有点的点权都增加 a 。
这时就需要用线段树维护子树。
4询问某个节点 x 到根的路径中所有点的点权和。
那这个怎么解决呢?就需要用树链剖分了。
并且树剖可以完美的吧这几个操作都一起解决,剩了很多事(但是代码还是很长)。
树剖,就是把一棵树按轻重链来分成若干条链。所谓重,就是指一个点的子节点很多。
化分的方法如图:
其中红线连接的是重儿子,并且每个点只有一个重儿子和连接它的一条重边。
而这些重边有会形成若干条链,每条重链都由一条轻边连接,这样就实现了轻重链划分。
而从任何一个点都可以先到该点属于的链的顶部,再跨过一条轻边,如此反复,一定能达到根节点。
这样,我们可以把每条链看作一段区间,用线段树lazy维护。
所以,实现步骤:
1:pre记录每个点的深度,儿子的个数和父亲。在求儿子时顺便求出重儿子。
2:求dfs序,并先走重儿子,这样才能在线段树中形成连续的区间。
同时记录每个点在线段树中的编号,和每个编号对应的点。(既可以通过点求编号,也可以通过编号知道是哪个点)。
另外,同时也要记录这个点所在的链的链顶,方便后面在链中跳跃。
3:线段树的建树等等。
4:在链中跳跃。(详见代码)
好了,说了这么多,来一道例题。
例题
分析
树剖模板题。
代码
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define full(a,b) memset(a,b,sizeof a)
#define N 100005
int n,m,arr[N];
int head[N],ecnt,nxt[2*N],to[2*N];
int dad[N],dep[N],size[N],son[N];
int top[N],id[N],rev[N];
long long ans,s[4*N],lazy[4*N];
void init()
{
full(head,-1);
}
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') f=ch=='-'?-1:1,ch=getchar();
while(ch>='0'&&ch<='9') x=10*x+ch-'0',ch=getchar();
return x*f;
}
void add(int u,int v)
{
to[++ecnt]=v;
nxt[ecnt]=head[u];
head[u]=ecnt;
}
void pre(int u,int fa)//求深度,父亲和子节点个数
{
dep[u]=dep[fa]+1;
dad[u]=fa;
size[u]=1;
for(int i=head[u]; i!=-1; i=nxt[i])
{
int v=to[i];
if(v==fa) continue;
pre(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfsx(int u)//求dfs序和链顶
{
int v=son[u];
if(v)
{
id[v]=++id[0];
top[v]=top[u];
rev[id[0]]=v;
dfsx(v);
}
for(int i=head[u]; i!=-1; i=nxt[i])
{
v=to[i];
if(top[v]) continue;
id[v]=++id[0];
top[v]=v;
rev[id[0]]=v;
dfsx(v);
}
}
//线段树↓↓↓
void built(int k,int l,int r)
{
if(l==r)
{
s[k]=arr[rev[l]];
return;
}
int mid=(l+r)>>1;
built(k*2,l,mid);
built(k*2+1,mid+1,r);
s[k]=s[2*k]+s[2*k+1];
}
void pushdown(int k,int l,int r)
{
if(!lazy[k]) return;
int mid=(l+r)>>1;
s[2*k]+=lazy[k]*(mid-l+1);
lazy[2*k]+=lazy[k];
s[2*k+1]+=lazy[k]*(r-mid);
lazy[2*k+1]+=lazy[k];
lazy[k]=0;
}
void update(int k,int l,int r,int x,int y,long long v)
{
if(x>r||y<l) return;
if(x<=l&&r<=y)
{
s[k]+=v*(r-l+1);
if(x!=y) lazy[k]+=v;
return;
}
int mid=(l+r)>>1;
pushdown(k,l,r);
update(2*k,l,mid,x,y,v);
update(2*k+1,mid+1,r,x,y,v);
s[k]=s[2*k]+s[2*k+1];
}
void ask(int k,int l,int r,int x,int y)
{
if(x>r||y<l) return;
if(x<=l&&r<=y)
{
ans+=s[k];
return;
}
int mid=(l+r)>>1;
pushdown(k,l,r);
ask(2*k,l,mid,x,y);
ask(2*k+1,mid+1,r,x,y);
}
//以上是线段树模板
void query(int u,int v)
{
int fu=top[u],fv=top[v];//记录两个点的链顶
while(fu!=fv)//没有到一起
{
if(dep[fu]<dep[fv]) swap(u,v),swap(fu,fv);//选择深度大的往上跳
ask(1,1,n,id[fu],id[u]);//更新线段树
u=dad[fu];fu=top[u];//跳跃
}
if(id[u]>id[v]) swap(u,v);
ask(1,1,n,id[u],id[v]);//处理最后的
}
int main()
{
// freopen("「HAOI2015」树上操作.in","r",stdin);
// freopen("「HAOI2015」树上操作.out","w",stdout);
init();
n=read(),m=read();
for(int i=1; i<=n; i++)
arr[i]=read();
for(int i=1; i<n; i++)
{
int x=read(),y=read();
add(x,y);add(y,x);
}
pre(1,0);
id[1]=++id[0];//根节点无法在dfsx中赋值,单独赋值
top[1]=1;
rev[1]=1;
dfsx(1);
built(1,1,n);
for(int i=1; i<=m; i++)
{
int flag=read();
if(flag==3)
{
int x=read();
ans=0;
query(1,x);//从X到根节点求和
printf("%lld\n",ans);
}
else
{
int x=read(),a=read();
if(flag==1) update(1,1,n,id[x],id[x],(long long)(a));//单点修改就是[x~x]
else update(1,1,n,id[x],id[x]+size[x]-1,(long long)(a));//id[x]+size[x]-1为X的子树
}
}
return 0;
}