dfs序基础+树上差分

dfs序基础1

给一棵有根树,这棵树由编号为 \(1\dots N\)\(N\) 个结点组成。根结点的编号为 \(R\)。每个结点都有一个权值,结点 \(i\) 的权值为 \(v_i\)
接下来有 \(M\) 组操作,操作分为两类:

  • 1 a x,表示将结点 \(a\) 的权值增加 \(x\)
  • 2 a,表示求结点 \(a\) 的子树上所有结点的权值之和。

输入 #1

10 14 9
12 -6 -4 -3 12 8 9 6 6 2
8 2
2 10
8 6
2 7
7 1
6 3
10 9
2 4
10 5
1 4 -1
2 2
1 7 -1
2 10
1 10 5
2 1
1 7 -5
2 5
1 1 8
2 7
1 8 8
2 2
1 5 5
2 6

输出 #1

21
34
12
12
23
31
4

$ 1\leqslant N, M\leqslant 10^6, 1\leqslant R\leqslant N, -10^6\leqslant v_i, x\leqslant 10^6. $

思路

\(dfs\)序,访问每一个节点,每到一个节点记录一次时间,同时时间\(++\)
\(u\) 的进入时间为 \(s[u]\) ,全部访问完子节点退出时的时间为 \(e[u]\)
重新编号 \(u\)\(s[u]\) , 那么发现对于
任一点 \(u\),它子树内的任一点 \(v\)\(s[u] <= v <= e[u]\) 即点 \(v\) 访问的时间被 \(u\) 的两个时间完全包含(u,v为重新编号为重新编号的),这时因为在访问完 \(u\) 内的所有点前不会退出到其它点

如图

博客园

上图中红色数字就是对应节点的 s, 蓝色数字就是对应节点的 e

把重编号后的节点进行操作,发现修改就是将节点对应下标的位置操作,而查询一个子树内的信息,就是查询一个区间内的信息

于是用树状数组维护就可以了

#include<bits/stdc++.h>
#define int long long
#define fore(i, a, b) for( int i = (a); i <= (b); ++ i)
#define repe(i, a, b) for( int i = (a); i >= (b); -- i)
using namespace std;
const int N = 1e6 + 10;
int n, m, r;
int dfn;
int s[N], e[N];
int t[N], a[N];
vector<int>G[N];
inline int lowbit(int x)
{
	return x & (-x);
}
void update(int u,int v)
{
	while(u <= n)
	{
		t[u] += v;
		u += lowbit(u);
	}
}
int sum(int u)
{
	int res = 0;
	while(u > 0)
	{
		res += t[u];
		u -= lowbit(u);
	}
	return res;
}
void dfs(int u,int fa)
{
	s[u] = ++ dfn;
	update(dfn, a[u]);
	fore(i, 0, G[u].size() - 1)
	{
		int v = G[u][i];
		if(v == fa)continue;
		dfs(v, u);
	}
	e[u] = dfn;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m >> r;
	fore(i, 1, n) cin >> a[i];
	fore(i, 1, n - 1)
	{
		int u, v;
		cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(r, 0);
	while(m --)
	{
		int orr, u, v;
		cin >> orr >> u;
		if(orr == 1)
		{
			cin >> v;
			update(s[u], v);
		}
		else
		{
			cout << sum(e[u]) - sum(s[u] - 1) << '\n';
		}
	}
	return 0;
}

dfs序基础2

给一棵有根树,这棵树由编号为 \(1\dots N\)\(N\) 个结点组成。根结点的编号为 \(R\)

每个结点都有一个权值,结点 \(i\) 的权值为 \(v_i\)

接下来有 \(M\) 组操作,操作分为两类:

  • 1 a x,表示将结点 \(a\) 的子树上所有结点的权值增加 \(x\)
  • 2 a,表示求结点 \(a\) 的子树上所有结点的权值之和。

输入 #1

10 14 9
12 -6 -4 -3 12 8 9 6 6 2
8 2
2 10
8 6
2 7
7 1
6 3
10 9
2 4
10 5
1 4 -1
2 2
1 7 -1
2 10
1 10 5
2 1
1 7 -5
2 5
1 1 8
2 7
1 8 8
2 2
1 5 5
2 6

输出 #1

21
33
16
17
27
76
30

\(1\leqslant N, M\leqslant 10^6, 1\leqslant R\leqslant N, -10^6\leqslant v_i, x\leqslant 10^6.\)

思路

这题和上题几乎一样,注意唯一区别在于本题的修改操作是对节点及其子树一起操作,所以是一个区间修改,区间查询的问题
使用线段树维护每一个点的权值即可

#include<bits/stdc++.h>
#define int long long
#define fore(i, a, b) for( int i = (a); i <= (b); ++ i)
#define repe(i, a, b) for( int i = (a); i >= (b); -- i)
using namespace std;
const int N = 1e6 + 10;
int n, m, Root , M;
int dfn;
int s[N], e[N];
int t[N], a[N];
int num[N];
int sum[N << 2], lazy[N << 2];
vector<int>G[N];
inline int ls(int p){return p << 1;}
inline int rs(int p){return ((p << 1) | 1);}
inline void pushup(int p){sum[p] = sum[ls(p)] + sum[rs(p)];}
void uy(int p,int l,int r,int k)
{
	sum[p] += (r - l + 1) * k;
	lazy[p] += k;
}
void pushdown(int p,int l,int r)
{
	int mid = (l + r) >> 1;
	if(lazy[p] && l != r)
	{
		sum[ls(p)] += lazy[p] * (mid - l + 1);
		sum[rs(p)] += lazy[p] * (r - mid);
		lazy[ls(p)] += lazy[p];
		lazy[rs(p)] += lazy[p];
		lazy[p] = 0;
	}
}
void build(int p,int l,int r)
{
	if(l == r)
	{
		sum[p] = a[num[l]];
		return;
	}
	int mid = (l + r) >> 1;
	build(ls(p), l, mid);
	build(rs(p), mid + 1, r);
	pushup(p);
	return;
}
void update(int p,int l,int r,int L,int R,int k)
{
	if(L <= l && r <= R)
	{
		uy(p, l, r, k);
		return;
	}
	int mid = (l + r) >> 1;
	pushdown(p, l, r);
	if(L <= mid) update(ls(p), l, mid, L, R, k);
	if(R >= mid + 1) update(rs(p), mid + 1, r, L, R, k);
	pushup(p);
	
}

int query(int p,int l,int r,int L,int R)
{
	if(L <= l && r <= R)
	{
		return sum[p];
	}
	int mid = (l + r) >> 1;
	pushdown(p, l, r);
	int res = 0;
	if(L <= mid) res += query(ls(p), l, mid, L, R);
	if(R >= mid + 1) res += query(rs(p), mid + 1, r, L, R);
	return res;
}

void dfs(int u,int fa)
{
	s[u] = ++ dfn;
	num[dfn] = u;
	fore(i, 0, G[u].size() - 1)
	{
		int v = G[u][i];
		if(v == fa)continue;
		dfs(v, u);
	}
	e[u] = dfn;
	return;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m >> Root;
	fore(i, 1, n) cin >> a[i];
	fore(i, 1, n - 1)
	{
		int u, v;
		cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(Root, 0);
	build(1, 1, n);
	while(m --)
	{
		int orr, u, v;
		cin >> orr >> u;
		if(orr == 1)
		{
			cin >> v;
			update(1, 1, n, s[u], e[u], v);
			
		}
		else
		{
			cout << query(1, 1, n, s[u], e[u]) << '\n';
			
		}
	}
	return 0;
}


P2982

题意简化就是每一次将一个点的权值加一,同时查询根节点到当前节点的权值和
发现当 \(u\) 的权值加 \(1\) 是作影响到的一定是他的子节点, 因为子树外的节点和根节点的路径不会经过它,所一用上一题的方法,每一次把一个点和他的所有子节点 \(+1\)
查询即可

#include<bits/stdc++.h>
#define int long long
#define fore(i, a, b) for( int i = (a); i <= (b); ++ i)
#define repe(i, a, b) for( int i = (a); i >= (b); -- i)
using namespace std;
const int N = 1e6 + 10;
int n, m, r;
int dfn;
int s[N], e[N], num[N];
int t[N];
vector<int>G[N];
inline int lowbit(int x)
{
	return x & (-x);
}
void update(int u,int v)
{
	while(u <= n)
	{
		t[u] += v;
		u += lowbit(u);
	}
}
int sum(int u)
{
	int res = 0;
	while(u > 0)
	{
		res += t[u];
		u -= lowbit(u);
	}
	return res;
}
void dfs(int u,int fa)
{
	s[u] = ++ dfn;
	num[dfn] = u;
	fore(i, 0, G[u].size() - 1)
	{
		int v = G[u][i];
		if(v == fa)continue;
		dfs(v, u);
	}
	e[u] = dfn;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin >> n;
	fore(i, 1, n - 1)
	{
		int u, v;
		cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1, 0);
	fore(i, 1, n)
	{
		int u;
		cin >> u;
		cout << sum(s[u]) << '\n';
		update(s[u], 1);
		update(e[u] + 1, -1);
	}
	return 0;
}








P3128

树上差分
题意是说每一次将一条路径上的所有点权值 \(+1\) 最后查询权值最大点的权值

想序列上的差分是怎样的?

\(l, r\) 差分时将 \(l\) 的点 \(+ val\)\(r\) 的点 $ - val$,最后求前缀和,就实现了对 \(l, r\) 进行 \(+ val\) 的操作,因为在前缀和时,在 \(l\) 加上的值会影响后面左右的点,但是为了避免他影响到\(r\)后面的点,就在\(r + 1\) 减去这个值

同样将差分运用的树上

\(u\)\(v\) 的路径操作,直接在 \(u\)\(v\) 加上一个值,在做一个类似求子树大小的东西,这样加上的值会一路往上传
但是在\(lca(u, v)\) -> \(p\) 是会多传一个,所以在 \(p\) 减一个,在p的父亲再减一个,防止影响上面的点

#include<bits/stdc++.h>
#define int long long
#define fore(i, a, b) for( int i = (a); i <= (b); ++ i)
#define repe(i, a, b) for( int i = (a); i >= (b); -- i)
using namespace std;
const int N = 1e5 + 10;
int n, k, ans = -1e9;
vector<int>G[N];
int dis[N], f[N][20];
int c[N];
void dfs(int u,int fa)
{
	dis[u] = dis[fa] + 1;
	f[u][0] = fa;
	fore(i, 1, 19)f[u][i] = f[f[u][i - 1]][i - 1];
	fore(i, 0, G[u].size() - 1)
	{
		int v = G[u][i];
		if(v == fa)continue;
		dfs(v, u);
	}
}
int lca(int x,int y)
{
	if(dis[x] < dis[y]) swap(x, y);
	repe(i, 19, 0)
	{
		if(dis[f[x][i]] >= dis[y]) x = f[x][i];
	}
	if(x == y)return x;
	repe(i, 19, 0)
	{
		if(f[x][i] != f[y][i])
		{
			x = f[x][i];
			y = f[y][i];
		}
	}
	return f[x][0];
}
void dfss(int u,int fa)
{
	fore(i, 0, G[u].size() - 1)
	{
		int v = G[u][i];
		if(v == fa)continue;
		dfss(v, u);
		c[u] += c[v];
	}
	ans = max(ans, c[u]);
}
signed main()
{
	ios::sync_with_stdio(false);
	cin >> n >> k;
	fore(i, 1, n - 1)
	{
		int u, v;
		cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1, 0);
	fore(i, 1, k)
	{
		int u, v, p;
		cin >> u >> v;
		p = lca(u, v);
		c[u] ++;
		c[v] ++;
		c[p] --;
		c[f[p][0]] --;
	}
	dfss(1, 0);
	cout << ans << '\n';
	return 0;
}









posted @ 2025-09-13 21:10  wmq2012  阅读(9)  评论(0)    收藏  举报