树剖裸题。
可以用树状数组,优化成O(nlogn)
只要找到根的路径上的值的和,考虑一次操作会影响到哪些位置查询的值。
采用差分。其实是对dfn序的区间加。
dfs找到dfn,dfn2
两个树状数组t1,t2;
操作1:t1.add(dfn[x],z),t1.add(dfn2[x]+1,-z) 子树差分,query的时候,得到的是路径上的单点加的标记。
操作2:对于在y(x的一个祖先)做子树加z的操作,对x的贡献是:(dep[x]-dep[y]+1)*z = dep[x]*z-(dep[y]-1)*z
差分一下。t1.add(dfn[y],-(dep[y]-1)*z) t1.add(dfn2[y]+1,(dep[y]-1)*z) t2.add(dfn[y],z) t2.add(dfn2[y]+1,-z)
这样,查询x的时候,就是t1.query(dfn[x])+dep[x]*t2.query(dfn[x]) ,t1包含了单点加的祖先的值,以及子树加部分的减去部分。t2上是一些标记。扫到一个,乘上dep[x],就把差分实现了。
代码:
#include<bits/stdc++.h> using namespace std; const int N=100000+5; typedef long long ll; int dfn[N],dfn2[N],dep[N]; int a[2*N],tot; ll w[N]; int n,m; struct arraytree{ ll f[2*N]; void upda(int x,ll c){for(;x<=2*n;x+=x&(-x))f[x]+=c;} ll query(int x){ll ret=0;for(;x;x-=x&(-x))ret+=f[x];return ret;} }t1,t2; struct node{ int nxt,to; }e[2*N]; int hd[N],cnt; void add(int x,int y){ e[++cnt].nxt=hd[x]; e[cnt].to=y; hd[x]=cnt; } void dfs(int x,int fa,int d){ dfn[x]=++tot; dep[x]=d; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; dfs(y,x,d+1); } dfn2[x]=tot; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",&w[i]);int x,y; for(int i=1;i<n;i++){ scanf("%d%d",&x,&y);add(x,y);add(y,x); } dfs(1,0,1); //for(int i=1;i<=2*n;i++)cout<<a[i]<<" ";cout<<endl; //for(int i=1;i<=n;i++) cout<<i<<" : "<<dfn[i]<<" "<<dfn2[i]<<endl; for(int i=1;i<=n;i++){ t1.upda(dfn[i],w[i]); t1.upda(dfn2[i]+1,-w[i]); } //for(int i=1;i<=n;i++) cout<<i<<" : "<<t1.query(dfn[i])<<endl; ll z;int op; for(int i=1;i<=m;i++){ scanf("%d",&op); if(op==1){ scanf("%d%lld",&x,&z); t1.upda(dfn[x],z); t1.upda(dfn2[x]+1,-z); } else if(op==2){ scanf("%d%lld",&x,&z); t1.upda(dfn[x],-(dep[x]-1)*z); t1.upda(dfn2[x]+1,(dep[x]-1)*z); t2.upda(dfn[x],z); t2.upda(dfn2[x]+1,-z); } else{ scanf("%d",&x); printf("%lld ",t1.query(dfn[x])+dep[x]*t2.query(dfn[x])); } } return 0; }