浅谈树上差分

P1 引入

序列上的差分,相当于树是一条链的情况。

d i = a i − a i − 1 ( i ≥ 2 ) d_i=a_i-a_{i-1}(i \ge 2) di=aiai1(i2)
d 1 = a 1 d_1=a_1 d1=a1

那么对于 [ x , y ] [x,y] [x,y] 的区间加:

d x ← d x + 1 d_x \larr d_x+1 dxdx+1
d y + 1 ← d y + 1 − 1 d_{y+1} \larr d_{y+1}-1 dy+1dy+11

最终值:

a i = ∑ j = 1 i d j a_i=\sum\limits_{j=1}^{i} d_j ai=j=1idj

	cin >> n >> k;
	while(k -- ) {
		cin >> x >> y;
		d[x] ++ , d[y+1] -- ;
	}
	
	int mx = 0;
	for(int i=1; i<=n; i++)
		a[i] = a[i-1] + d[i],
		mx = max(mx, a[i]);
	cout << mx;

同样的我们也可以用从 n n n 开始后缀和的形式:

	cin >> n >> k;
	while(k -- ) {
		cin >> x >> y;
		d[y] ++ , d[x-1] --;
	}
	
	int mx = 0;
	for(int i=n; i>=1; i--)
		a[i] = a[i+1] + d[i],
		mx = max(mx, a[i]);
	cout << mx;

由此可见,不同的前缀和方式对应着不同的差分、求具体值方式。

P2 点差分

题目

给定一棵树,多次操作,每次操作在树上一条简单路径上的点都加 1 1 1,问操作后所有点权最大值。

一般情况中,类比 P1,我们首先需要确定求具体值方式。

a u a_u au 表示结点 u u u 的点权大小。

在树上有两种求结点 u u u 的具体值方式:

  • 自上而下:从根节点开始到 u u u 求和求具体值。

    这种情况比较容易想到,每个点的差分 d u d_u du 表示的是 u u u 与其父亲的差值,即 d u = a u − a f a u d_u=a_u-a_{fa_u} du=auafau

    这样对于单次路径的修改,我们要将 lca 处的结点 +1,并将所有路径上的点相连的非路径上的点 -1,这样修改的点数是至多 n n n 个,时间复杂度是 O ( n ) O(n) O(n) 级别的,不符合要求。

  • 自下而上:从叶子结点到结点 u u u 求和求具体值。

    此时差分表示的是 u u u 与其所有儿子 v v v 的和的差值,即 d u = a u − ∑ v ∈ s o n u a v d_u = a_u - \sum\limits_{v \in son_u} a_v du=auvsonuav

    根据求和方式,我们考虑将 x x x y y y 的路径拆分为两条链:

    x → . . . → lca(x,y) x \to ... \to \text{lca(x,y)} x...lca(x,y) lca(x,y) → . . . → y \text{lca(x,y)} \to ... \to y lca(x,y)...y 。(注意不要重复统计 lca \text{lca} lca 处)

    这样对于单次路径修改,我们只需修改 x , y x,y x,y 以及 lca(x,y),fa[lca(x,y)] \text{lca(x,y),fa[lca(x,y)]} lca(x,y),fa[lca(x,y)] 即可,时间复杂度 O ( 1 ) O(1) O(1)

核心代码:

void dfs2(int u, int fa)
{
    a[u] = d[u];
    for(auto v : G[u])
    {
        if(v == fa) continue;
        dfs2(v, u);
        a[u] += a[v];
    }
    mx = max(mx, a[u]);
}

int main()
{
    ...
    while(k -- )
    {
        int x, y;
        cin >> x >> y;
        int LCA = lca(x, y);
        
        d[x] ++ , d[LCA] -- ;
        d[y] ++ , d[f[LCA][0]] -- ;
    }
    dfs2(1, 0);
    cout << mx;
    return 0;
}


P3 边差分

给定一棵树,多次操作,每次操作在树上一条简单路径上的边都加 1 1 1,问操作后所有边权最大值。

考虑转边权为点权: a u a_u au 表示结点 u u u f a u fa_u fau 间边的权值。

对于在链上的操作 x → . . . → s o n y → y x \to ... \to son_y \to y x...sonyy ,根据 a a a 的定义,我们应当将 x x x s o n y son_y sony 之间的点的 a a a 都加 1 1 1 ,差分即 d x ← d x + 1 , d y ← d y − 1 d_x \larr d_x +1,d_y \larr d_y-1 dxdx+1,dydy1

对于简单路径 x x x y y y 的加操作,即拆分为两条链上路径 [ x , l c a ) [x,lca) [x,lca) [ y , l c a ) [y,lca) [y,lca)

	...
	while(k -- )
    {
        int x, y;
        cin >> x >> y;
        int LCA = lca(x, y);
        d[x] ++ , d[y] ++ , d[LCA] -= 2;
    }
    dfs2(1, 0);
    cout << mx;

你可能感兴趣的:(学习笔记,c++,算法,开发语言)