官方题解
https://ac.nowcoder.com/discuss/160376?type=101&order=0&pos=5&page=1
思路来源
https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40398427
官方题解
先离线把所有操作都读进来,把树形建好,处理dfs序
注意到一棵子树的树根root的dfs序比其子节点都小,
且这棵子树的dfs序在[root,root+siz[root]-1]之间,所以可以区间修改加值
每次修改时,不妨对其整棵子树进行修改,
由于对某个点的查询一定是在这个点插入之后的,
所以在插入的时候再初始化即可,消除之前的影响
由于只有单点询问,维护差分数组的线段树,
[l,r]加v的时候,在l处加v,在r+1处减v即可,
询问x这一点的值的时候,查询[1,x]的前缀和即可
树状数组处理这类问题相当得心应手
然后op==1类型的操作,插入这个点,相当于给这个点初始化赋0,
那么先查询一下这个点的值v,然后令[l,l]的区间减去v即可,即起到了赋0的效果
学了一年半ACM,补OI爷出的题终于有点感觉了
代码不长,却又很考验思维的题,用这些短小精悍的题提升码力再好不过了
注意时间戳dfn必须从1起,和树状数组一致
多数组的建图写法也是好评,比原来开struct edge的代码剪短了不少
也算是真正开dfs序的入门题了叭抄树链剖分板子不算
#include<iostream> #include<cstdio> #include<cstring> const int maxn=1e5+10; int to[maxn],nex[maxn]; int head[maxn],num,cnt; int siz[maxn]; int dfn[maxn]; int tree[maxn]; int n,m; struct query { int op,i,a; }a[4*maxn]; void add(int u,int v) { to[++cnt]=v; nex[cnt]=head[u]; head[u]=cnt; } void dfs(int u) { siz[u]++; dfn[u]=++n; for(int i=head[u];i;i=nex[i]) { int v=to[i]; dfs(v); siz[u]+=siz[v]; } } void update(int x,int v) { for(int i=x;i<=n;i+=i&-i) tree[i]+=v; } int ask(int x) { int res=0; for(int i=x;i;i-=i&-i) res+=tree[i]; return res; } int main() { scanf("%d",&m); for(int i=1;i<=m;++i) { scanf("%d%d",&a[i].op,&a[i].i); if(a[i].op==2)scanf("%d",&a[i].a); if(a[i].op==1) { num++; add(a[i].i,num); a[i].i=num;//将1询问的结点赋为新的根节点 } } dfs(0);//节点可以0开头,但时间戳映射到差分BIT上一定从1开始 for(int i=1;i<=m;++i) { if(a[i].op==1) { int v=ask(dfn[a[i].i]); update(dfn[a[i].i],-v);//由于非负 子树一定大 对树根清零 子树一定非负 update(dfn[a[i].i]+1,v);//这里如果+1是只对树根清0 +siz[a[i].i]则对整棵树清零 } else if(a[i].op==2) { update(dfn[a[i].i],a[i].a); update(dfn[a[i].i]+siz[a[i].i],-a[i].a); } else printf("%d\n",ask(dfn[a[i].i])); } return 0; }