牛客小白月赛12 H-华华和月月种树(dfs序+差分树状数组)

题目

思路来源

官方题解

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;
}