@xiaoziyao
2020-08-04T16:13:41.000000Z
字数 1427
阅读 1040
解题报告
题意:给你一颗个结点带点权的树,支持两个操作:①把某个点权值加,它的所有儿子权值加,它所有儿子的儿子权值加……②求某点权值
数据范围:。
这是一道比较好的dfn序练习题。
dfn序,即某个点在dfs时第一次遍历的时间戳,有一个很优秀的性质:某个点的子树(包括它自己)中所有点的dfn序恰好是一段连续的序列。这个性质很显然,用dfs的搜索顺序想就很容易理解了。
看到这道题,我们发现它的修改都是关于子树的,所以我们可以想到用dfn序把树拍平成序列来维护。
但是它还有一个问题,如何让它的子树间断地加减权值呢?其实这也很简单,这个操作等价于这个点所有级儿子加,所有级儿子减,所有级儿子加……因为dfs的顺序,所以我们可以知道dfn序的另一个性质——若两个点层数奇偶性不同,那么它们的dfn序奇偶性也不同。
这样就好办了,由于是区间修改,单点查询,所以可以建两个树状数组,一个维护奇数层次,一个维护偶数层次,然后修改差分一下,查询直接求就可以了。
#include<stdio.h>
#define lowbit(x) x&-x
const int maxn=200005,maxm=400005,maxt=400005;
int i,j,k,m,n,e,cnt;
int start[maxn],to[maxm],then[maxm],sum[maxt][2],dfn[maxn],end[maxn],tp[maxn],a[maxn];
inline void add(int x,int y){
then[++e]=start[x],start[x]=e,to[e]=y;
}
void update(int x,int v,int t){
for(int i=x;i<maxt;i+=lowbit(i))
sum[i][t]+=v;
}
int query(int x,int t){
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=sum[i][t];
return res;
}
void dfs(int x,int last,int t){
dfn[x]=++cnt,tp[x]=t;
for(int i=start[x];i;i=then[i]){
int y=to[i];
if(y==last)
continue;
dfs(y,x,t^1);
}
end[x]=cnt;
}
int main(){
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
for(i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
dfs(1,0,0);
for(i=1;i<=m;i++){
int opt,x,v;
scanf("%d",&opt);
if(opt==1){
scanf("%d%d",&x,&v);
update(dfn[x],v,tp[x]);
update(end[x]+1,-v,tp[x]);
}
if(opt==2){
scanf("%d",&x);
printf("%d\n",a[x]+query(dfn[x],tp[x])-query(dfn[x],tp[x]^1));
}
}
return 0;
}