题目
思路来源
官方题解
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;
}