华华和月月种树
知识点
树剖+树状数组
思路
容我吐槽一下,这牛客LCA题单全都是点树剖,真不如叫树剖题单(doge)
好啦,我们正式来说这题。
我们先正常分析,遇见问题我们在过来调整思路。
首先,我们不考虑第一个操作,来看剩下两个。
这里面,首先我们看到了一个操作。
操作 2:输入格式 2 i a,表示华华上线做任务使节点 i 的子树中所有节点(即它和它的所有子孙节点)权值加 a 。
其实如果只是这个操作的话,我们可以考虑维护一个树上差分数组sum,这样我们就可以把区间操作变成单点操作,每次只需要对根节点+a,每个节点到根节点的路径的和就是在该点的权值。
但是我们发现还有单点询问的操作。
询问 3:输入格式3 i,华华需要给出 i 节点此时的权值。
此时可以发现,我们要进行的操作就是,动态的进行单点修改和区间查询,这我们立马就想到了树状数组。为了利用起来树状数组,我们要加一个树剖。
现在我们来看看操作1
操作 1:输入格式1 i,表示月月氪金使节点 i 长出了一个新的儿子节点,权值为0,编号为当前最大编号 +1(也可以理解为,当前是第几个操作 1,新节点的编号就是多少)。
这一开始令我非常苦恼,因为如果树会动态变化的话,那我们就没办法建立起来树剖。
最后,我想到了一个还算比较巧妙的做法。
做法
- 首先,我们将所有操作存下来,其中我们依据给的父子关系,将树建立起来。
- 接着,我们根据树将树剖建立起来
- 然后,我们再顺序的执行三个操作。
- 对于操作2,我们直接将根节点下面的子树节点全部+a(包括此时还没有创立出来的)
- 对于操作1,此时
i下边的u才刚刚被建立出来,则我们用一个数组ans记录之前被多加的部分。 - 对于询问3,此时对于
u节点来说,其本身的值应当为u节点到根节点的路径权值和-ans[u]
Ac_code
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5 + 10,M = N<<1;
struct Node
{
int op,u,a;
}OP[N];
int h[N],e[N],ne[N],idx;
int fa[N],son[N],sz[N];
int dfn[N],ts;
int ans[N],cnt;
int tr[N];
int n,id;
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void insert(int x,int c)
{
while(x<=n)
{
tr[x] += c;
x += x & -x;
}
}
int sum(int x)
{
int res = 0;
while(x)
{
res += tr[x];
x -= x & -x;
}
return res;
}
void dfs1(int u)
{
sz[u] = 1;
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
dfs1(j);
sz[u] += sz[j];
if(sz[j]>sz[son[u]]) son[u] = j;
}
}
void dfs2(int u)
{
dfn[u] = ++ts;
if(!son[u]) return ;
dfs2(son[u]);
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==son[u]) continue;
dfs2(j);
}
}
int main()
{
scanf("%d",&n);
memset(h,-1,sizeof h);
id = 1;
for(int i=0;i<n;i++)
{
int op,u,a;scanf("%d%d",&op,&u);
u++;
if(op==2) scanf("%d",&a);
if(op==1) add(u,++id);
if(op!=1) OP[i] = {op,u,a};
else OP[i] = {op,id};
}
dfs1(1);dfs2(1);
for(int i=0;i<n;i++)
{
int op = OP[i].op,u = OP[i].u,a = OP[i].a;
if(op==1) ans[u] = sum(dfn[u]);
if(op==2) insert(dfn[u],a),insert(dfn[u]+sz[u],-a);
if(op==3) cout<<sum(dfn[u])-ans[u]<<"\n";
}
return 0;
}

浙公网安备 33010602011771号