题目描述
有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:
- 操作 1 :把某个节点 x 的点权增加 a 。
- 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
- 操作 3 :询问某个节点 x 到根的路径中所有点的点权和。
输入格式
第一行包含两个整数 N, M 。表示点数和操作数。
接下来一行 N 个整数,表示树中节点的初始权值。
接下来 N-1 行每行两个正整数 from, to , 表示该树中存在一条边 (from, to) 。
再接下来 M 行,每行分别表示一次操作。其中第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。
输出格式
对于每个询问操作,输出该询问的答案。答案之间用换行隔开。
输入输出样例
输入 #1
5 5 1 2 3 4 5 1 2 1 4 2 3 2 5 3 3 1 2 1 3 5 2 1 2 3 3
输出 #1
6 9 13
说明/提示
对于 100% 的数据, N,M<=100000 ,且所有输入数据的绝对值都不会超过 10^6 。
一道树链剖分模板题。。。,比洛谷树链剖分模板题还简单
【模板】树链剖分需要支持路径修改,子树修改,子树查询和路径查询
这道题只需要支持单点修改,子树修改和路径查询,而且路径的左端点还固定为1,其实这道题应该是蓝题的。。。
哦对了,重要的事情说三遍:
开long long,开long long,开long long
不会树链剖分的小伙伴可以参考以下博客
想联系树链剖分的同学们也可以参考以下题目
好了废话不多数,放代码吧
代码如下:
#include<bits/stdc++.h> using namespace std; struct SYM{ int to,next; }edge[200010]; struct ASJ{ long long sum; long long lz; }tree[400010]; int head[100010],tot; int n,m; int w[100010],dep[100010],fa[100010],son[100010],siz[100010],top[100010],wet[100010],id[100010]; void addedge(int x,int y){ edge[++tot].to=y; edge[tot].next=head[x]; head[x]=tot; } void build(int i,int l,int r){ //建树 if(l==r){ tree[i].sum=wet[l]; return ; } int mid=(l+r)/2; build(2*i,l,mid); //左儿子 build(2*i+1,mid+1,r); //右儿子 tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum); } void pushdown(int i,long long len){ //LAZY下传 tree[2*i].lz+=tree[i].lz; tree[2*i+1].lz+=tree[i].lz; tree[2*i].sum+=(tree[i].lz*(len-len/2)); tree[2*i+1].sum+=(tree[i].lz*(len/2)); tree[i].lz=0; //别忘了清零 } void update(int i,int l,int r,int L,int R,long long k){//更新操作 if(l>=L&&r<=R){ tree[i].sum+=k*(r-l+1); tree[i].lz+=k; return ; } int mid=(l+r)/2; pushdown(i,(r-l+1)); //下传LAZY if(L<=mid) update(2*i,l,mid,L,R,k); if(R>mid) update(2*i+1,mid+1,r,L,R,k); tree[i].sum=tree[2*i].sum+tree[2*i+1].sum; } long long query(int i,int l,int r,int L,int R){//查询操作 long long ans=0; if(l>=L&&r<=R){ return tree[i].sum; } int mid=(l+r)/2; pushdown(i,(r-l+1)); if(L<=mid) ans+=query(2*i,l,mid,L,R); if(R>=mid+1) ans+=query(2*i+1,mid+1,r,L,R); return ans; } //----------------------------------------------------------------上面是线段树 void dfs1(int now,int from){ //处理dep,fa,siz,以及重儿子son dep[now]=dep[from]+1; fa[now]=from; int maxson=-1; siz[now]=1; for(int i=head[now];i;i=edge[i].next){ int v=edge[i].to; if(v==from) continue; dfs1(v,now); siz[now]+=siz[v]; if(siz[v]>maxson){ son[now]=v; maxson=siz[v]; } } } int cnt; void dfs2(int now,int topr){ //处理重链链顶top,新点id,新点权值wet id[now]=++cnt; top[now]=topr; wet[cnt]=w[now]; if(!son[now]) return; dfs2(son[now],topr); //先处理重儿子,再处理轻儿子 for(int i=head[now];i;i=edge[i].next){ int v=edge[i].to; if(v==fa[now]||v==son[now]) continue; dfs2(v,v); //每个轻儿子都是一个新的链顶,别忘了换链顶!!! } } void update1(int x,int k){ update(1,1,n,id[x],id[x]+siz[x]-1,k); //子树是连续的所以左节点id[x],右节点id[x]+siz[x]-1 } long long q1(int x,int y){ //这里我写的有点麻烦,因为一个点固定为根1,所以其实可以省略一些,不过这里的代码是可以应用于每一个树链剖分路经查询的 long long ans=0; while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); ans+=query(1,1,n,id[top[x]],id[x]); x=fa[top[x]]; } if(dep[x]>dep[y]) swap(x,y); ans+=query(1,1,n,id[x],id[y]); return ans; } int main(){ freopen("sscz.in","r",stdin); freopen("sscz.out","w",stdout); int no,x,y; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&w[i]); for(int i=1;i<n;i++){ scanf("%d%d",&x,&y); addedge(x,y); addedge(y,x); } dfs1(1,0); dfs2(1,1); build(1,1,n); while(m--){ scanf("%d",&no); if(no==1){ scanf("%d%d",&x,&y); update(1,1,n,id[x],id[x],y); //单点修改 } if(no==2){ scanf("%d%d",&x,&y); //子树修改 update1(x,y); } if(no==3){ //路径查询 scanf("%d",&x); printf("%lld ",q1(x,1)); } } }