树上差分

43 阅读2分钟

如何维护树上差分

多次对树上的一些路径做加法操作,然后询问某个点或某条边经过操作后的值,就要考虑树上差分了。

差分之后的树满足点 u 的子树和等于 u 点原来的点权。

  • 点差分

例如,初态树上的各点点权为 0,现对路径 (x, y) 上的点均做 +1 操作,等价于 dx+1, dy+1, dlca1, dfa[lca]1d_x+1,\ d_y+1,\ d_{lca}-1,\ d_{fa[lca]}-1

  • 边差分

例如,初态树上的各点点权为 0,现对路径 (x, y) 上的边均做 +1 操作。边权操作比较困难,通常把点权下移给节点,变为点权操作,等价于 dx+1, dy+1, dlca2d_x+1,\ d_y+1,\ d_{lca}-2

  • 初态的各点点权不为 0 如何初始化 ?

对于叶子结点 leaf,差分值 bub_u = 点权值 wuw_u

对于非叶子结点 u,它的差分值 bub_u 加上所有儿子的差分子树和 = wuw_u, 所以差分值 bub_u = wuw_u - vwv\sum_v w_v

例题一 P3128 [USACO15DEC] Max Flow P

www.luogu.com.cn/problem/P31…

#include<bits/stdc++.h>
using namespace std;
#define int long long

constexpr int N = 5E4 + 10, LOG = __lg(N) + 3;
vector<int> e[N];
int n, m, dep[N], fa[N][LOG], w[N];

// 倍增预处理
void dfs(int u, int f) {
	dep[u] = dep[f] + 1;
	fa[u][0] = f;
	for(int i = 1; i < LOG; i ++)
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	for(auto v : e[u]) {
		if(v != f) dfs(v, u);
	}
}
// 求 lca
int lca(int u, int v) {
	if(dep[u] < dep[v]) swap(u, v);
	for(int i = LOG - 1; i >= 0; i --) {
		if(dep[fa[u][i]] >= dep[v]) {
			u = fa[u][i];
		}
	}
	if(u == v) return u;
	for(int i = LOG - 1; i >= 0; i --) {
		if(fa[u][i] != fa[v][i]) {
			u = fa[u][i];
			v = fa[v][i];
		}
	}
	return fa[u][0];
}

int ans = -1E9;

void dfs2(int u, int f) {
	for(auto v : e[u]) {
		if(v == f) continue ;
		dfs2(v, u);
		w[u] += w[v];
	}
	ans = max(ans, w[u]);
}

void solve(){
	cin >> n >> m;
	for(int i = 1; i < n; i ++){
		int x, y;
		cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
    }
    dfs(1, 0); // 预处理倍增
    for(int i = 1; i <= m; i ++){
    	int x, y;
    	cin >> x >> y;
    	int l = lca(x, y);
    	w[x] ++, w[y] ++, w[l] --, w[fa[l][0]] --;
    }
    dfs2(1, 0); // 统计答案
    cout << ans;
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0); 
	solve();
	return 0;
}