P3178 [HAOI2015] 树上操作
P3178 [HAOI2015] 树上操作
题目描述
有一棵点数为
- 操作
:把某个节点 的点权增加 。 - 操作
:把某个节点 为根的子树中所有点的点权都增加 。 - 操作
:询问某个节点 到根的路径中所有点的点权和。
提示
对于
Solution:
十分有趣的 dfs 序+线段树。
我们思考一下贡献如何产生:我们用线段树维护每个点到根节点这条路径上的权值。
那么操作1就相当于在
对于操作2:我们考虑这个操作对其子树内一个点
实现:
我们维护一颗线段树,每个节点维护两类贡献
然后这题就做完了。
Code:
#include<bits/stdc++.h> #define int long long const int N=1e5+5; using namespace std; int n,m,tot; int dep[N],st[N],ed[N],rid[N]; vector<int> E[N]; void dfs(int x,int fa) { st[x]=++tot;dep[x]=dep[fa]+1; for(auto y : E[x])if(y!=fa)dfs(y,x); ed[x]=tot;rid[st[x]]=x; } struct Segment_Tree{ struct Tree{ int tag[2],val[2]; }t[N<<2]; #define ls x<<1 #define rs x<<1|1 inline void add(int x,int k,int id){t[x].tag[id]+=k,t[x].val[id]+=k;} inline void pushdown(int x) { if(t[x].tag[0]){add(ls,t[x].tag[0],0);add(rs,t[x].tag[0],0);t[x].tag[0]=0;} if(t[x].tag[1]){add(ls,t[x].tag[1],1);add(rs,t[x].tag[1],1);t[x].tag[1]=0;} } void upd(int x,int l,int r,int L,int R,int k,int id) { if(L<=l&&r<=R){add(x,k,id);return;} int mid=l+r>>1;pushdown(x); if(L<=mid)upd(ls,l,mid,L,R,k,id); if(mid<R)upd(rs,mid+1,r,L,R,k,id); } int query(int x,int l,int r,int pos) { if(l==r)return dep[rid[pos]]*t[x].val[0]+t[x].val[1]; int mid=l+r>>1;pushdown(x); if(pos<=mid)return query(ls,l,mid,pos); else return query(rs,mid+1,r,pos); } }T; int a[N]; void work() { cin>>n>>m; for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); } for(int i=2,x,y;i<=n;i++) { scanf("%lld%lld",&x,&y); E[x].push_back(y);E[y].push_back(x); } dfs(1,0); for(int i=1;i<=n;i++)T.upd(1,1,n,st[i],ed[i],a[i],1); for(int i=1,opt,x,y;i<=m;i++) { scanf("%lld%lld",&opt,&x); if(opt==1) { scanf("%lld",&y); T.upd(1,1,n,st[x],ed[x],y,1); } if(opt==2) { scanf("%lld",&y); T.upd(1,1,n,st[x],ed[x],-y*(dep[x]-1),1); T.upd(1,1,n,st[x],ed[x],y,0); } if(opt==3) { int ans=T.query(1,1,n,st[x]); printf("%lld\n",ans); } } } #undef int int main() { //freopen("P3178.in","r",stdin);freopen("P3178.out","w",stdout); work(); return 0; }