树的dfs序 整合

定义

dfs序是指:每个节点在dfs深度优先遍历中的进出栈的时间序列。
所以它有什么用呢
我们知道,树是一种非线性的数据结构,它的一些数据调用肯定是没有线性结构来得方便的。所以这个时候,dfs站了出来。基于dfs函数,我们可以在遍历的同时记录下每个节点进出栈的时间序列。就假设有这样一颗树:
你瞅啥
dfs序就是:

再瞅试试我们发现,一个点的进出栈的时间点之间的时间段就是它的子树在栈中的所有时间。也就是说,子树的dfs序肯定大于根节点的进栈时间小于根节点的出栈时间,这就成了一个区间问题。所以我们就把一个树上的问题“拍”到了一个线性的数据结构上面。区间问题就是贼好做的了,有各种强大的数据结构可以用来维护区间,例如线段树(不会线段树的同学可以看博主的blog:线段树by柴犬首相),树状数组啊之类的。然后我们可以随便搞了

性质

现在来介绍dfs序列一些有用的性质:

  1. 任意子树都是连续的。例如假设有个子树$BEFK$,在序列中对应的部分是:$BEEFKKFB$;子树$CGHI$,在序列中对应的部分是:$CGGHHIIC$。
  2. 任意点对$(a,b)$之间的路径,可分为两种情况,首先是令$lca$是$a、b$的最近公共祖先:
    1.若$lca$是$a、b$之一,则$a、b$之间的$in$时刻的区间或者$out$时刻区间就是其路径。例如$AK$之间的路径就对应区间$ABEEFK$或者$KFBCGGHHIICA$。
    2.若$lca$另有其人,则$a、b$之间的路径为$In[a]、Out[b]$之间的区间或者$In[b]、Out[a]$之间的区间。另外,还需额外加上$lca$!!!考虑$EK$路径,对应为$EFK$再加上$B$。考虑$EH$之间的路径,对应为$EFKKFBCGGH$再加上$A$。

利用这些性质,可以利用DFS序列完成子树操作和路径操作,同时也有可能将莫队算法应用到树上从而得到树上莫队。

例题1

BZOJ4034
这道题目是树上的操作。需要在树上完成3类操作,单点更新,子树更新,以及根到指定节点的路径查询。利用性质1以及性质2.1即可完成,连$LCA$都无需求出。对整个DFS序列使用线段树进行维护,注意到整个序列实际上有正有负,因此额外用一个域来表示正数的个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;

int const SIZE = 100100;
typedef long long weight_t;

struct edge_t{
int to;
int next;
}Edge[SIZE<<1];
int Vertex[SIZE];
int ECnt;
weight_t W[SIZE];

inline void mkEdge(int a,int b){
Edge[ECnt].to = b;
Edge[ECnt].next = Vertex[a];
Vertex[a] = ECnt++;

Edge[ECnt].to = a;
Edge[ECnt].next = Vertex[b];
Vertex[b] = ECnt++;
}

int InIdx[SIZE],OutIdx[SIZE];
int InOut[SIZE<<1];
int NewIdx[SIZE<<1];
int NCnt;

void dfs(int node,int parent){
NewIdx[NCnt] = node;
InOut[NCnt] = 1;
InIdx[node] = NCnt++;
for(int next=Vertex[node];next;next=Edge[next].next){
int son = Edge[next].to;
if ( son != parent ) dfs(son,node);
}
NewIdx[NCnt] = node;
InOut[NCnt] = -1;
OutIdx[node] = NCnt++;
}

int N;
weight_t StSum[SIZE<<3];
weight_t Lazy[SIZE<<3];
int Flag[SIZE<<3];//The count of the positive number in the range

inline int lson(int x){return x<<1;}
inline int rson(int x){return lson(x)|1;}

inline void _pushUp(int t){
StSum[t] = StSum[lson(t)] + StSum[rson(t)];
Flag[t] = Flag[lson(t)] + Flag[rson(t)];
}

inline void _pushDown(int t){
if ( 0LL == Lazy[t] ) return;

weight_t& x = Lazy[t];

int son = lson(t);
StSum[son] += Flag[son] * x;
Lazy[son] += x;

son = rson(t);
StSum[son] += Flag[son] * x;
Lazy[son] += x;

x = 0LL;
}

void build(int t,int s,int e){
Lazy[t] = 0LL;
if ( s == e ){
StSum[t] = InOut[s] * W[NewIdx[s]];
Flag[t] = InOut[s];
return;
}

int m = ( s + e ) >> 1;
build(lson(t),s,m);
build(rson(t),m+1,e);
_pushUp(t);
}

void modify(int t,int s,int e,int a,int b,weight_t delta){
if ( a <= s && e <= b ){
StSum[t] += Flag[t] * delta;
Lazy[t] += delta;
return;
}

_pushDown(t);
int m = ( s + e ) >> 1;
if ( a <= m ) modify(lson(t),s,m,a,b,delta);
if ( m < b ) modify(rson(t),m+1,e,a,b,delta);
_pushUp(t);
}

weight_t query(int t,int s,int e,int a,int b){
if ( a <= s && e <= b ){
return StSum[t];
}

_pushDown(t);

weight_t ret = 0LL;
int m = ( s + e ) >> 1;
if ( a <= m ) ret += query(lson(t),s,m,a,b);
if ( m < b ) ret += query(rson(t),m+1,e,a,b);
return ret;
}

inline weight_t query(int x){
return query(1,1,N<<1,1,InIdx[x]);
}

inline void modify(int x,weight_t delta){
modify(1,1,N<<1,InIdx[x],InIdx[x],delta);
modify(1,1,N<<1,OutIdx[x],OutIdx[x],delta);
}

inline void modifySubtree(int x,weight_t delta){
modify(1,1,N<<1,InIdx[x],OutIdx[x],delta);
}

inline void initTree(int n){
ECnt = NCnt = 1;
fill(Vertex,Vertex+n+1,0);
}

int M;
bool read(){
if ( EOF == scanf("%d%d",&N,&M) ) return false;

initTree(N);
for(int i=1;i<=N;++i)scanf("%lld",W+i);

int a,b;
for(int i=1;i<N;++i){
scanf("%d%d",&a,&b);
mkEdge(a,b);
}

dfs(1,0);
build(1,1,N<<1);
return true;
}

void proc(){
int cmd,x;
weight_t a;
while(M--){
scanf("%d%d",&cmd,&x);
switch(cmd){
case 1:scanf("%lld",&a);modify(x,a);break;
case 2:scanf("%lld",&a);modifySubtree(x,a);break;
case 3:printf("%lld\n",query(x));break;
}
}
}
int main(){
while( read() ) proc();
return 0;
}

例题2

给定一棵n个节点的树,m次查询,每次查询需要求出某个节点深度为h的所有子节点。

分析

对于这个问题如果试图去对每个节点保存所有深度的子节点,在数据大的时候内存会吃不消;或者每次查询的时候去遍历一遍,当数据大的时候,时间效率会非常低。
此时如果使用dfs序维护树结构就可以轻松地解决这个问题。
作为预处理,首先将将树的所有节点按深度保存起来,每个深度的所有节点用一个线性结构保存,每个深度的节点相对顺序要和前序遍历一致。
然后从树的根节点进行dfs,对于每个节点记录两个信息,一个是dfs进入该节点的时间戳in[id],另一个是dfs离开该节点的时间戳out[id]。
最后对于每次查询,求节点v在深度h的所有子节点,只需将深度为h并且dfs进入时间戳在in[v]和out[v]之间的所有节点都求出来即可,由于对于每个深度的所有节点,相对顺序和前序遍历的顺序以致,那么他们的dfs进入时间戳也是递增的,于是可以通过二分搜索求解。

步骤1

如下图,可以看到,由于普通的树并不具有区间的性质,所以在考虑使用线段树作为解题思路时,需要对给给定的数据进行转化,首先对这棵树进行一次dfs遍历,记录dfs序下每个点访问起始时间与结束时间,记录起始时间是前序遍历,结束时间是后序遍历,同时对这课树进行重标号。
这里写图片描述

步骤2

如下图,DFS之后,那么树的每个节点就具有了区间的性质。
这里写图片描述
那么此时,每个节点对应了一个区间,而且可以看到,每个节点对应的区间正好“管辖”了它子树所有节点的区间,那么对点或子树的操作就转化为了对区间的操作。
【PS: 如果对树的遍历看不懂的话,不妨待会对照代码一步一步调试,或者在纸上模拟过程~】

步骤3

就如上文提到的,处理好之后可以开始用线段树和树状数组等数据结构进行实现了。

经典题目合集

“所谓DFS序, 就是DFS整棵树依次访问到的结点组成的序列”
“DFS序有一个很强的性质: 一颗子树的所有节点在DFS序内是连续的一段, 利用这个性质我们可以解决很多问题”
基本代码 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Dfs(int u, int fa, int dep)
{
seq[++cnt] = u;
st[u] = cnt;
int len = edge[u].size();
for(int i = 0; i < len; i++) {
int v = edge[u][i];
if(v != fa) {
Dfs(v, u, dep+1);
}
}
seq[++cnt] = u;
ed[u] = cnt;
}

假设我们有给定一颗树, 和每个节点的权值,然后要计算下列问题

题1

对某个节点X权值加上一个数W, 查询某个子树X里所有点权的和。
解 :
由于X子树在DFS序中是连续的一段, 只需要维护一个序列,
支持单点修改和区间查询, 用树状数组就能实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int MAXN = 1e5+10;

vector<int> edge[MAXN];
int s[2*MAXN];
int seq[2*MAXN];
int st[MAXN];
int ed[MAXN];
int cnt;

int Lowbit(int x)
{
return x & (-x);
}

void Add(int x, int val, int n)
{
for(int i = x; i <= n; i += Lowbit(i)) {
s[i] += val;
}
}

int Sum(int x)
{
int res = 0;
for(int i = x; i > 0; i -= Lowbit(i)) {
res += s[i];
}
return res;
}

void Dfs(int u, int fa)
{
seq[++cnt] = u;
st[u] = cnt;
int len = edge[u].size();
for(int i = 0; i < len; i++) {
int v = edge[u][i];
if(v != fa) {
Dfs(v, u);
}
}
seq[++cnt] = u;
ed[u] = cnt;
}

void Init(int n)
{
for(int i = 0; i <= n; i++) {
edge[i].clear();
}
memset(s, 0, sizeof(s));
}

int main()
{
int n, op;
int u, v, w;
int cmd;

while(scanf("%d %d", &n, &op) != EOF) {
Init(n);
for(int i = 0; i < n-1; i++) {
scanf("%d %d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
cnt = 0;
Dfs(1, -1);
while(op--) {
scanf("%d", &cmd);
if(cmd == 0) {
scanf("%d %d", &u, &w);
Add(st[u], w, cnt);
}
else if(cmd == 1) {
scanf("%d", &u);
printf("%d\n", Sum(ed[u]) - Sum(st[u] - 1));
}
}
}

return 0;
}

题2

对节点X到Y的最短路上所有点权都加一个数W, 查询某个点的权值
解 :
这个操作等价于
a. 对X到根节点路径上所有点权加W
b. 对Y到根节点路径上所有点权加W
c. 对LCA(x, y)到根节点路径上所有点权值减W
d. 对LCA(x,y)的父节点 parent(LCA(x, y))到根节点路径上所有权值减W
于是要进行四次这样从一个点到根节点的区间修改
将问题进一步简化, 进行一个点X到根节点的区间修改, 查询其他一点Y时
只有X在Y的子树内, X对Y的值才有贡献且贡献值为W
于是只需要更新四个点, 查询一个点的子树内所有点权的和即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int MAXN = 1e5+10;

vector<int> edge[MAXN];
int s[2*MAXN];
int seq[2*MAXN];
int seq1[2*MAXN];
int depth[2*MAXN];
int first[MAXN];
int dp[2*MAXN][25];
int st[MAXN];
int ed[MAXN];
int parent[MAXN];
int cnt, num;

int Lowbit(int x)
{
return x & (-x);
}

void Add(int x, int val, int n)
{
if(x <= 0) return;
for(int i = x; i <= n; i += Lowbit(i)) {
s[i] += val;
}
}

int Sum(int x)
{
int res = 0;
for(int i = x; i > 0; i -= Lowbit(i)) {
res += s[i];
}
return res;
}

void Dfs(int u, int fa, int dep)
{
parent[u] = fa;
seq[++cnt] = u;
seq1[++num] = u;
first[u] = num;
depth[num] = dep;
st[u] = cnt;
int len = edge[u].size();
for(int i = 0; i < len; i++) {
int v = edge[u][i];
if(v != fa) {
Dfs(v, u, dep+1);
seq1[++num] = u;
depth[num] = dep;
}
}
seq[++cnt] = u;
ed[u] = cnt;
}

void RMQ_Init(int n)
{
for(int i = 1; i <= n; i++) {
dp[i][0] = i;
}
for(int j = 1; (1 << j) <= n; j++) {
for(int i = 1; i + (1 << j) - 1 <= n; i++) {
int a = dp[i][j-1], b = dp[i + (1 << (j-1))][j-1];
dp[i][j] = depth[a] < depth[b] ? a : b;
}
}
}

int RMQ_Query(int l, int r)
{
int k = 0;
while((1 << (k + 1)) <= r - l + 1) k++;
int a = dp[l][k], b = dp[r-(1<<k)+1][k];
return depth[a] < depth[b] ? a : b;
}

int LCA(int u, int v)
{
int a = first[u], b = first[v];
if(a > b) a ^= b, b ^= a, a ^= b;
int res = RMQ_Query(a, b);
return seq1[res];
}

void Init(int n)
{
for(int i = 0; i <= n; i++) {
edge[i].clear();
}
memset(s, 0, sizeof(s));
}

int main()
{
int n, op;
int u, v, w;
int cmd;

while(scanf("%d %d", &n, &op) != EOF) {
Init(n);
for(int i = 0; i < n-1; i++) {
scanf("%d %d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
cnt = 0, num = 0;
Dfs(1, -1, 0);
RMQ_Init(num);
while(op--) {
scanf("%d", &cmd);
if(cmd == 0) {
scanf("%d %d %d", &u, &v, &w);
int lca = LCA(u, v);
Add(st[u], w, cnt);
Add(st[v], w, cnt);
Add(lca, -w, cnt);
Add(parent[lca], -w, cnt);
}
else if(cmd == 1) {
scanf("%d", &u);
printf("%d\n", Sum(ed[u]) - Sum(st[u] - 1));
}
}
}

return 0;
}

题3

对节点X到Y的最短路上所有点权都加一个数W, 查询某个点子树的权值之和
解 :
同问题2中的修改方法, 转化为修改某点到根节点的权值加/减W
当修改某个节点A, 查询另一节点B时
只有A在B的子树内, Y的值会增加$ W \times (depth[A] - depth[B] + 1) == W \times (depth[A] + 1) - W \times depth[B]$
同样是用树状数组来查询Y的子树, 修改 和 查询方法都要新增一个数组
第一个数组保存 $W \times (depth[A] + 1)$, 第二个数组保存 W
每次查询结果为$Sum(ed[B]) - Sum(st[B]-1) - (Sum1(ed[B]) - Sum(st[B]-1)) \times depth[B]$

题4

对某个点X权值加上一个数W, 查询X到Y路径上所有点权之和
解 :
求X到Y路径上所有的点权之和, 和前面X到Y路径上所有点权加一个数相似
这个问题转化为
X到根节点的和 + Y到根节点的和 $- LCA(x, y)$到根节点的和 $- parent(LCA(x,y)) $到根节点的和
于是只要支持单点修改, 区间查询即可
要注意的是修改, 要在该点开始出现位置加W, 在结束位置减W
这样能保证在该节点子树内的能加到这个W, 不在该点子树内的无影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int MAXN = 1e5+10;

vector<int> edge[MAXN];
int s[2*MAXN];
int s1[2*MAXN];
int seq[2*MAXN];
int seq1[2*MAXN];
int depth[2*MAXN];
int first[MAXN];
int dp[2*MAXN][25];
int st[MAXN];
int ed[MAXN];
int parent[MAXN];
int cnt, num;

int Lowbit(int x)
{
return x & (-x);
}

void Add(int x, int val, int n)
{
if(x <= 0) return;
for(int i = x; i <= n; i += Lowbit(i)) {
s[i] += val;
}
}

int Sum(int x)
{
int res = 0;
for(int i = x; i > 0; i -= Lowbit(i)) {
res += s[i];
}
return res;
}

void Dfs(int u, int fa, int dep)
{
parent[u] = fa;
seq[++cnt] = u;
seq1[++num] = u;
first[u] = num;
depth[num] = dep;
st[u] = cnt;
int len = edge[u].size();
for(int i = 0; i < len; i++) {
int v = edge[u][i];
if(v != fa) {
Dfs(v, u, dep+1);
seq1[++num] = u;
depth[num] = dep;
}
}
seq[++cnt] = u;
ed[u] = cnt;
}

void RMQ_Init(int n)
{
for(int i = 1; i <= n; i++) {
dp[i][0] = i;
}
for(int j = 1; (1 << j) <= n; j++) {
for(int i = 1; i + (1 << j) - 1 <= n; i++) {
int a = dp[i][j-1], b = dp[i + (1 << (j-1))][j-1];
dp[i][j] = depth[a] < depth[b] ? a : b;
}
}
}

int RMQ_Query(int l, int r)
{
int k = 0;
while((1 << (k + 1)) <= r - l + 1) k++;
int a = dp[l][k], b = dp[r-(1<<k)+1][k];
return depth[a] < depth[b] ? a : b;
}

int LCA(int u, int v)
{
int a = first[u], b = first[v];
if(a > b) a ^= b, b ^= a, a ^= b;
int res = RMQ_Query(a, b);
return seq1[res];
}

void Init(int n)
{
for(int i = 0; i <= n; i++) {
edge[i].clear();
}
memset(s, 0, sizeof(s));
}

int main()
{
int n, op;
int u, v, w;
int cmd;

while(scanf("%d %d", &n, &op) != EOF) {
Init(n);
for(int i = 0; i < n-1; i++) {
scanf("%d %d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
cnt = 0, num = 0;
Dfs(1, -1, 0);
RMQ_Init(num);
while(op--) {
scanf("%d", &cmd);
if(cmd == 0) {
scanf("%d %d", &u, &w);
Add(st[u], w, cnt);
Add(ed[u], -w, cnt);
}
else if(cmd == 1) {
scanf("%d %d", &u, &v);
int lca = LCA(u, v);
printf("%d\n", Sum(st[u]) + Sum(st[v]) - Sum(st[lca]) - Sum(st[parent[lca]]));
}
}
}

return 0;
}

题5

对子树X里所有节点加上一个值W, 查询某个点的值
解 :
对DFS序来说, 子树内所有节点加W, 就是一段区间加W
所以这个问题就是 区间修改, 单点查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int MAXN = 1e5+10;

vector<int> edge[MAXN];
int s[2*MAXN];
int s1[2*MAXN];
int seq[2*MAXN];
int seq1[2*MAXN];
int depth[2*MAXN];
int first[MAXN];
int dp[2*MAXN][25];
int st[MAXN];
int ed[MAXN];
int parent[MAXN];
int cnt, num;

int Lowbit(int x)
{
return x & (-x);
}

void Add(int x, int val, int n)
{
if(x <= 0) return;
for(int i = x; i <= n; i += Lowbit(i)) {
s[i] += val;
}
}

int Sum(int x)
{
int res = 0;
for(int i = x; i > 0; i -= Lowbit(i)) {
res += s[i];
}
return res;
}

void Dfs(int u, int fa)
{
seq[++cnt] = u;
st[u] = cnt;
int len = edge[u].size();
for(int i = 0; i < len; i++) {
int v = edge[u][i];
if(v != fa) {
Dfs(v, u);
}
}
seq[++cnt] = u;
ed[u] = cnt;
}

void Init(int n)
{
for(int i = 0; i <= n; i++) {
edge[i].clear();
}
memset(s, 0, sizeof(s));
}

int main()
{
int n, op;
int u, v, w;
int cmd;

while(scanf("%d %d", &n, &op) != EOF) {
Init(n);
for(int i = 0; i < n-1; i++) {
scanf("%d %d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
cnt = 0;
Dfs(1, -1);
while(op--) {
scanf("%d", &cmd);
if(cmd == 0) {
scanf("%d %d", &u, &w);
Add(st[u], w, cnt);
Add(ed[u], -w, cnt);
}
else if(cmd == 1) {
scanf("%d", &u);
printf("%d\n", Sum(u));
}
}
}

return 0;
}

题6

对子树X里所有节点加上一个值W, 查询某个子树的权值和
解 :
子树所有节点加W, 就是某段区间加W, 查询某个子树的权值和, 就是查询某段区间的和
区间修改区间求和, 是线段树的经典问题, 用线段树可以很好解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

typedef struct {
int l, r, sum, add;
} Seg;

const int MAXN = 1e5+10;

Seg T[4*MAXN];
vector<int> edge[MAXN];
int s[2*MAXN];
int seq[2*MAXN];
int st[MAXN];
int ed[MAXN];
int cnt;

void Dfs(int u, int fa)
{
seq[++cnt] = u;
st[u] = cnt;
int len = edge[u].size();
for(int i = 0; i < len; i++) {
int v = edge[u][i];
if(v != fa) {
Dfs(v, u);
}
}
seq[++cnt] = u;
ed[u] = cnt;
}

void Build(int l, int r, int k)
{
T[k].l = l, T[k].r = r, T[k].sum = T[k].add;
if(l == r) return;
int mid = (l + r) >> 1;
Build(l, mid, k<<1);
Build(mid+1, r, k<<1|1);
}

void PushDown(int k)
{
if(T[k].add) {
T[k<<1].sum += (T[k<<1].r - T[k<<1].l + 1) * T[k].add;
T[k<<1].add += T[k].add;
T[k<<1|1].sum += (T[k<<1|1].r - T[k<<1|1].l + 1) * T[k].add;
T[k<<1|1].add += T[k<<1].add;
T[k].add = 0;
}
}

void PushUp(int k)
{
T[k].sum = T[k<<1].sum + T[k<<1|1].sum;
}

void Update(int l, int r, int val, int k)
{
if(T[k].l == l && T[k].r == r) {
T[k].sum += (r - l + 1) * val;
T[k].add += val;
return ;
}
PushDown(k);
int mid = (T[k].l + T[k].r) >> 1;
if(r <= mid) Update(l, r, val, k<<1);
else if(l > mid) Update(l, r, val, k<<1|1);
else {
Update(l, mid, val, k<<1);
Update(mid+1, r, val, k<<1|1);
}
PushUp(k);
}

int Query(int l, int r, int k)
{
if(T[k].l == l && T[k].r == r) {
return T[k].sum;
}
PushDown(k);
int mid = (T[k].l + T[k].r) >> 1;
if(r <= mid) return Query(l, r, k<<1);
else if(l > mid) return Query(l, r, k<<1|1);
else {
return Query(l, mid, k<<1) + Query(mid+1, r, k<<1|1);
}
}

void Init(int n)
{
for(int i = 0; i <= n; i++) {
edge[i].clear();
}
}

int main()
{
int n, op;
int u, v, w;
int cmd;

while(scanf("%d %d", &n, &op) != EOF) {
Init(n);
for(int i = 0; i < n-1; i++) {
scanf("%d %d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
cnt = 0;
Dfs(1, -1);
Build(1, cnt, 1);
while(op--) {
scanf("%d", &cmd);
if(cmd == 0) {
scanf("%d %d", &u, &w);
Update(st[u], ed[u], w, 1);
}
else if(cmd == 1) {
scanf("%d", &u);
printf("%d\n", Query(st[u], ed[u], 1) / 2);
}
}
}

return 0;
}

题7

对节点X的子树所有节点加上一个值W, 查询X到Y的路径上所有点的权值和
解:
同问题4把路径上求和转化为四个点到根节点的和
X到根节点的和 + Y到根节点的和 $- LCA(x, y)$到根节点的和 $- parent(LCA(x,y)) $到根节点的和
修改一点A, 查询某点B到根节点时, 只有B在A的子树内, A对B才有贡献
贡献为$W \times (depth[B] - depth[A] + 1) == W \times (1 - depth[A]) + W \times depth[B]$
和第三题一样, 用两个树状数组维护 $W \times(depth[A] + 1), W \times depth[B]$
同样要注意修改某点时, 在一点开始位置加, 在其结束位置减.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

typedef struct {
int l, r, sum, add;
} Seg;

const int MAXN = 1e5+10;

Seg T[4*MAXN];
vector<int> edge[MAXN];
int s[2*MAXN];
int s1[2*MAXN];
int seq[2*MAXN];
int seq1[2*MAXN];
int depth[2*MAXN];
int first[MAXN];
int dp[2*MAXN][25];
int parent[MAXN];
int st[MAXN];
int ed[MAXN];
int cnt, cnt1;

int Lowbit(int x)
{
return x & (-x);
}

void Add(int x, int val, int n)
{
if(x <= 0) return ;
for(int i = x; i <= n; i += Lowbit(i)) {
s[i] += val;
}
}

void Add1(int x, int val, int n)
{
if(x <= 0) return ;
for(int i = x; i <= n; i += Lowbit(i)) {
s1[i] += val;
}
}

int Sum(int x)
{
int res = 0;
for(int i = x; i > 0; i -= Lowbit(i)) {
res += s[i];
}
return res;
}

int Sum1(int x)
{
int res = 0;
for(int i = x; i > 0; i -= Lowbit(i)) {
res += s1[i];
}
return res;
}

void RMQ_Init(int n)
{
for(int i = 1; i <= n; i++) {
dp[i][0] = i;
}
for(int j = 1; (1 << j) <= n; j++) {
for(int i = 1; i + (1 << j) - 1 <= n; i++) {
int a = dp[i][j-1], b = dp[i + (1 << (j-1))][j-1];
dp[i][j] = depth[a] < depth[b] ? a : b;
}
}
}

int RMQ_Query(int l, int r)
{
int k = 0;
while((1 << (k + 1)) <= r - l + 1) k++;
int a = dp[l][k], b = dp[r-(1 << k)+1][k];
return depth[a] < depth[b] ? a : b;
}

int LCA(int u, int v)
{
int a = first[u], b = first[v];
if(a > b) a ^= b, b ^= a, a ^= b;
int res = RMQ_Query(a, b);
return seq1[res];
}

void Dfs(int u, int fa, int dep)
{
seq[++cnt] = u;
seq1[++cnt1] = u;
first[u] = cnt1;
parent[u] = fa;
depth[cnt1] = dep;
st[u] = cnt;
int len = edge[u].size();
for(int i = 0; i < len; i++) {
int v = edge[u][i];
if(v != fa) {
Dfs(v, u, dep+1);
seq1[++cnt1] = u;
depth[cnt1] = dep;
}
}
seq[++cnt] = u;
ed[u] = cnt;
}

void Init(int n)
{
for(int i = 0; i <= n; i++) {
edge[i].clear();
}
memset(s, 0, sizeof(s));
memset(s1, 0, sizeof(s1));
}

void Debug()
{
int u, v;
while(1) {
scanf("%d %d", &u, &v);
printf("The LCA of %d %d is %d\n", u, v, LCA(u, v));
}
}

int main()
{
int n, op;
int u, v, w;
int cmd;

while(scanf("%d %d", &n, &op) != EOF) {
Init(n);
for(int i = 0; i < n-1; i++) {
scanf("%d %d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
cnt = cnt1 = 0;
Dfs(1, 0, 0);
RMQ_Init(cnt1);
while(op--) {
scanf("%d", &cmd);
if(cmd == 0) {
scanf("%d %d", &u, &w);
Add(st[u], w * (1 - depth[first[u]]), cnt);
Add(ed[u], -w * (1 - depth[first[u]]), cnt);
Add1(st[u], w, cnt);
Add1(ed[u], -w, cnt);
}
else if(cmd == 1) {
scanf("%d %d", &u, &v);
int lca = LCA(u, v);
int par = parent[lca];
int ans = Sum(st[u]);
ans += depth[first[u]] * Sum1(st[u]);
ans += Sum(st[v]);
ans += depth[first[v]] * Sum1(st[v]);
ans -= Sum(st[lca]);
ans -= depth[first[lca]] * Sum1(st[lca]);
ans -= Sum(st[par]);
ans -= depth[first[par]] * Sum1(st[par]);
printf("%d\n", ans);
}
}
}

return 0;
}
文章目录
  1. 1. 定义
  2. 2. 性质
  3. 3. 例题1
  4. 4. 例题2
    1. 4.1. 分析
      1. 4.1.1. 步骤1
      2. 4.1.2. 步骤2
      3. 4.1.3. 步骤3
  5. 5. 经典题目合集
    1. 5.1.
    2. 5.2. 假设我们有给定一颗树, 和每个节点的权值,然后要计算下列问题
    3. 5.3. 题1
    4. 5.4. 题2
    5. 5.5. 题3
    6. 5.6. 题4
    7. 5.7. 题5
    8. 5.8. 题6
    9. 5.9. 题7