P3128 [USACO15DEC] Max Flow P 树上差分LCA
解题思路
本题是一道典型的树上差分问题,结合了LCA(最近公共祖先)算法。题目要求我们统计树中每个节点被多少条路径覆盖,并找出被覆盖次数最多的节点。
关键步骤
- 
LCA预处理:使用倍增法预处理每个节点的各层祖先信息,以便快速查询任意两点的LCA 
- 
树上差分: - 
对于每条路径s→t,在s和t节点处+1 
- 
在它们的LCA处-1 
- 
在LCA的父节点处-1 
 
- 
- 
统计计算:通过后序遍历统计每个节点被覆盖的次数,并维护最大值 
#include<bits/stdc++.h> using namespace std; const int N = 5e5 + 10; // f[i][j]表示节点i的2^j级祖先 int f[N][26]; // dep[i]表示节点i的深度 int dep[N]; // d[i]表示节点i的差分值 int d[N]; // 存储最终答案 int ans; // 邻接表存储树结构 vector<int> g[N]; int n,m; // DFS预处理LCA信息 void dfs(int x,int fa) { f[x][0] = fa; // 直接父节点 dep[x] = dep[fa] + 1; // 计算深度 // 预处理2^i级祖先 for(int i = 1; (1 << i) <= dep[x]; i++) { int y = f[x][i - 1]; f[x][i] = f[y][i - 1]; // 2^i级祖先是2^(i-1)级祖先的2^(i-1)级祖先 } // 递归处理子节点 for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(y != fa) dfs(y,x); } } // 后序遍历统计差分结果 void dfs2(int x,int fa) { for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(y == fa) continue; dfs2(y,x); d[x] += d[y]; // 累加子节点的差分值 } ans = max(ans,d[x]); // 更新最大值 } // LCA查询函数 int lca(int x,int y) { // 确保x是较深的节点 if(dep[x] < dep[y]) swap(x,y); // 将x跳到与y同一深度 for(int i = 20; i >= 0; i--) if(dep[x] - (1 << i) >= dep[y]) x = f[x][i]; // 如果此时已经相同,直接返回 if(x == y) return x; // 同时向上跳跃查找 for(int i = 20; i >= 0; i--){ if(f[x][i] != f[y][i]){ // 祖先不同才跳跃 x = f[x][i],y = f[y][i]; } } return f[x][0]; // 返回最终的LCA } int main() { cin >> n >> m; // 构建树结构 for(int i = 1; i < n; i++) { int x,y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } // 预处理LCA信息 dfs(1,0); // 处理每条路径 for(int i = 1; i <= m; i++) { int x,y; scanf("%d%d",&x,&y); int rt = lca(x,y); // 计算LCA d[x]++, d[y]++; // 起点和终点+1 d[rt]--; // LCA处-1 if(f[rt][0]) d[f[rt][0]]--; // LCA的父节点-1(如果存在) } // 统计最终结果 dfs2(1,0); cout << ans; return 0; }
算法分析
- 
时间复杂度: - 
LCA预处理:O(NlogN) 
- 
每条路径处理:O(logN)(LCA查询) 
- 
统计计算:O(N) 
- 
总复杂度:O(NlogN + KlogN + N) ≈ O((N+K)logN) 
 
- 
- 
空间复杂度: - 
O(NlogN)(存储倍增表) 
 
- 
- 
优势: - 
高效处理大规模树结构(N可达5×10^4) 
- 
可以处理大量路径查询(K可达10^5) 
- 
差分技巧将路径更新转化为O(1)操作 
 
- 
树上差分算法公式推导
1. 基本概念
树上差分是一种处理树结构路径修改的高效算法,能在O(1)时间完成路径标记,最后通过一次DFS遍历计算出每个节点的最终值。
2. 核心问题
给定一棵树和若干路径(u,v),需要对路径上所有节点进行加减操作,最后求每个节点的值。
3. 差分数组思想
借鉴一维差分思想:
- 
原数组:A[] 
- 
差分数组:D[i] = A[i] - A[i-1] 
- 
区间[l,r]加x:D[l]+x, D[r+1]-x 
4. 树上差分公式推导
对于树结构,我们使用点差分和边差分两种形式:
4.1 点差分
路径u→v上所有节点加x:
D[u] += x D[v] += x D[lca(u,v)] -= x D[parent[lca(u,v)]] -= x # 如果是点权,需要减去双倍
4.2 边差分
路径u→v上所有边加x(假设边权保存在子节点):
D[u] += x D[v] += x D[lca(u,v)] -= 2x
5. 数学推导
设:
- 
f(u)表示从根到u的路径和 
- 
路径u→v的和 = f(u) + f(v) - 2*f(lca) 
要使得路径上所有点加x:
- 
相当于对u→root和v→root路径加x 
- 
但lca→root被加了两次,需要减去 
- 
最终影响: 
 Δf = x*(depth[u]+depth[v]-2*depth[lca]+1)
6. 实现步骤
- 
预处理LCA(倍增/Tarjan) 
- 
应用差分公式 
- 
后序遍历累加差分值 
7. 示例代码(C++)
void dfs(int u, int p) { for(int v : tree[u]) { if(v != p) { dfs(v, u); diff[u] += diff[v]; } } value[u] += diff[u]; } // 点差分操作 void point_update(int u, int v, int x) { int l = lca(u, v); diff[u] += x; diff[v] += x; diff[l] -= x; if(parent[l] != -1) diff[parent[l]] -= x; }
8. 复杂度分析
- 
预处理LCA:O(nlogn) 
- 
每次操作:O(1) 
- 
最终计算:O(n) 
9. 应用场景
- 
网络流量统计 
- 
子树求和 
- 
路径染色问题 
树上点差分的正确性推导
1. 基本逻辑误区澄清
在点差分操作中,对 LCA 和其父节点都减去 x 的操作看起来确实有些反直觉,但这是为了精确控制影响范围。让我们通过具体例子来理解。
2. 关键概念图解
考虑以下树结构:
A / \ B C / \ D E
假设我们要对路径 D→C 上的所有节点加 x(即节点 D、B、A、C)
3. 错误做法分析
如果仅按照线性差分的直觉:
D[D] += x D[C] += x D[A] -= x # 只减去LCA
这样计算的结果:
- 
D的值:x (正确) 
- 
B的值:x (错误,不应被影响) 
- 
A的值:x (正确) 
- 
C的值:x (正确) 
- 
E的值:0 (正确) 
问题出在节点B被错误地加了x!
4. 正确的差分公式
正确的点差分操作:
D[D] += x D[C] += x D[A] -= x # LCA D[parent[A]] -= x # 通常设为虚拟根节点的父节点(若存在)
5. 数学证明
设 f(u) 是从根到u的路径和,则:
- 
对D和C加x相当于: - 
把D→root和C→root两条路径都+x 
 
- 
- 
这两条路径的交集是A→root 
- 
因此需要: - 
在LCA(A)处减去x(消除一次重复) 
- 
在A的父节点处再减x(完全消除多余影响) 
 
- 
6. 实际影响范围验证
A(-x) / \ (+x)B C(+x) / (+x)D
最终影响:
- 
D: +x (D→root) 
- 
B: +x -x = 0 (正确) 
- 
A: +x +x -x = +x (正确) 
- 
C: +x (正确) 
7. 为什么需要双重减法?
因为:
- 
第一次减法(LCA处)消除两条路径在LCA以下的重叠 
- 
第二次减法(LCA的父节点)消除两条路径在LCA以上的重叠 
8. 边界情况处理
当LCA就是根节点时:
D[u] += x D[v] += x D[root] -= x D[null] -= x # 可忽略
9. 标准模板代码
void point_update(int u, int v, int x) { int l = lca(u, v); diff[u] += x; diff[v] += x; diff[l] -= x; if(parent[l] != -1) diff[parent[l]] -= x; }
10. 记忆口诀
"加两端,减LCA,再减LCA的爸"
这种设计确保了修改严格限定在u-v路径上的所有节点,不会影响其他无关节点。这是树上差分区别于线性差分的核心特征。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号