前置知识点
dfs遍历
树状数组/线段树知识
链接
题意
已知有 n 个节点,有 n−1 条边,形成一个树的结构。
给定一个根节点 k,每个节点都有一个权值,节点i的权值为 vi
给 m 个操作,操作有两种类型:
1 a x :表示将节点 a 的权值加上 x
2 a :表示求 aa 节点的子树上所有节点的和(包括 a 节点本身)
坑爹的是什么?坑爹的是结点个数和操作数都是1e6级别的!
思路
第一次尝试,就是正常的dfs建树,然后dfs求出子树和,更新就老老实实向上更新,把更新求完sum的target设为1。。。
然后就老老实实地TLE了。
最坏情况可是一个1e6的树链呀!这样就O(n^2)怎么都不够了。
正确的思路:dfs序+树状数组
(一)dfs
首先需要dfs搞一下树。黑色序号为默认的输入顺序,红色编号为dfs的遍历顺序。我们需要使用红色的编号作为dfs序。为什么呢?我们可以看到,使用了红色编号之后,每个结点的子树编号都是连续的了。
(以下编号皆为红色的dfs序)
结点1:1~6
结点2:2~4
结点3:3~3
结点4:4~4
结点5:5~5
结点6:6~6
我们在dfs遍历的同时,把每个结点的子树结点(包括本身)的起始坐标和末坐标保存在数组in[] 和数组out[]里面
当然你也可以设置一个dfn[] 序列
dfn[i]=j 表示红色编号i代表的是黑色编号j的点 【好绕啊有没有!】
见代码!
int Time=0; ///时间戳
void dfs(int x,int fa){
in[x]=++Time;
for(int i=0;i<P[x].size();++i){
int v=P[x][i];
if(v!=fa)dfs(v,x);
}
out[x]=Time;
}
(二)树状数组
我们搞了那么久,就是让维护的区间都是一段连续的。
比如对于结点i的子树和那就等于in[i] ~ out[i] 之间的和
那就使用线段树或者树状数组吧!这里树状数组方便一丢丢。
首先树状数组基本代码搞出来!
ll c[MAX];
ll lowbit(int x){
return x&(-x);
}
ll query(int x){
ll ans = 0ll;
while(x){
ans += c[x];
x -= lowbit(x);
}
return ans;
}
void update(int x,ll v){
while(x <= n){
c[x] += v;
x += lowbit(x);
}
}
很明显,因为只有dfs序的序列才应该是树状数组所维护的c[]数组,而不是原来就输入的a[]数组。
我们每次更新,更新的下标为时间戳Time(就是dfs序的当前下标),更新的值为a[x],即当前dfs到的那个数的值。
void dfs(int x,int fa){
in[x]=++Time;
update(Time,a[x]); ///这里加上这句!
for(int i=0;i<P[x].size();++i){
int v=P[x][i];
if(v!=fa)dfs(v,x);
}
out[x]=Time;
}
而对于之后的操作中的更新,我们也有办法。
对于结点x,in[x]就是该结点的dfs序,那么就简单了
if(ope==1){
cin >> p1 >> p2;
update(in[p1],p2); ///下标和增加值
}
对于询问操作,因为结点x管理的子树范围是从in[x] ~ out[x],而树状数组的询问操作又是前缀和,因此我们使用一个差就可以了!注意in那里需要减掉一个1,这样才能保证差就是in[x] ~ out[x]的值。
cout << query(out[p1])-query(in[p1]-1)<< endl;
完整代码如下!
#include <bits/stdc++.h>
#define show(x) std::cerr << #x << "=" << x << std::endl
#define IOS ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
/** freopen("input.in", "r", stdin);
freopen("output.out", "w", stdout);*/
typedef long long ll;
const int MAX=1e6+50;
const int INF=1e9;
const double EPS = 0.001;
const ll MOD=1e9+7;
vector<int>P[MAX];
int in[MAX],out[MAX];
int n,Time;
int a[MAX];
ll c[MAX];
ll lowbit(int x){
return x&(-x);
}
ll query(int x){
ll ans = 0ll;
while(x){
ans += c[x];
x -= lowbit(x);
}
return ans;
}
void update(int x,ll v){
while(x <= n){
c[x] += v;
x += lowbit(x);
}
}
void dfs(int x,int fa){
in[x]=++Time;
update(Time,a[x]);
for(int i=0;i<P[x].size();++i){
int v=P[x][i];
if(v!=fa)dfs(v,x);
}
out[x]=Time;
}
int main()
{
IOS;
int m,k;
cin >> n >> m >> k;
for(int i=1;i<=n;++i){
cin >> a[i];
}
for(int i=1;i<n;++i){
int va,vb;
cin >> va >> vb;
P[va].push_back(vb);
P[vb].push_back(va);
}
dfs(k,0);
for(int i=0;i<m;++i){
int ope,p1;
ll p2;
cin >> ope;
if(ope==1){
cin >> p1 >> p2;
update(in[p1],p2);
}else{
cin >> p1;
///show(p1);show(in[p1]);show(out[p1]);
cout << query(out[p1])-query(in[p1]-1)<< endl;
}
}
return 0;
}
/**
5 6 1
1 2 3 4 5
1 3
1 2
2 4
2 5
1 2 10
1 3 10
1 4 5
1 5 1
2 3
2 2
====
output:
13
27
*/
这题也是经过一些dl的指点和百度、看了很多代码才明白的!
(之前还没听说过dfs序这种东东唔唔)
当时之后写的时候用线段树写一直段错误和WA,差点自闭
相信一切都会好起来的!