原题链接:https://www.luogu.com.cn/problem/P3128
题意解读:一棵树,每次选取两个节点,在这两个节点之间路径运送牛奶,每运送一次,经过的所有节点都会增加1,最终统计记录最大的节点。
解题思路:
要计算树上两个节点之间的路径,可以采用LCA算法,确定路径之后如果将路径中所有点都加1呢?
1、暴力法
直接枚举,加1,复杂度O(n),一共要操作k次,总体复杂度O(nlogn + kn)
2、树上差分
借助于树上差分操作,可以O(1)复杂度对要路径上的点进行+1操作,最后还原前缀和数组就是每个节点运送牛奶的次数,总体复杂度O(nlogn+k+n)
树上差分详解
边差分
点差分
示例代码
#include <iostream>
#include <vector>
using namespace std;const int MAXN = 100005;
vector<int> adj[MAXN]; // 邻接表存储树的结构
int depth[MAXN]; // 存储每个节点的深度
int parent[MAXN][20]; // 用于求 LCA,parent[u][i] 表示节点 u 的 2^i 级祖先
int diff_edge[MAXN]; // 边差分标记数组
int diff_point[MAXN]; // 点差分标记数组// 深度优先搜索,计算每个节点的深度和父节点
void dfs(int u, int p, int d) {depth[u] = d;parent[u][0] = p;for (int i = 1; i < 20; i++) {parent[u][i] = parent[parent[u][i - 1]][i - 1];}for (int v : adj[u]) {if (v != p) {dfs(v, u, d + 1);}}
}// 求两个节点的最近公共祖先
int lca(int u, int v) {if (depth[u] < depth[v]) swap(u, v);for (int i = 19; i >= 0; i--) {if (depth[u] - (1 << i) >= depth[v]) {u = parent[u][i];}}if (u == v) return u;for (int i = 19; i >= 0; i--) {if (parent[u][i] != parent[v][i]) {u = parent[u][i];v = parent[v][i];}}return parent[u][0];
}// 边差分操作,对路径 u - v 上的边进行修改
void edge_diff(int u, int v, int val) {int l = lca(u, v);diff_edge[u] += val;diff_edge[v] += val;diff_edge[l] -= 2 * val;
}// 点差分操作,对路径 u - v 上的点进行修改
void point_diff(int u, int v, int val) {int l = lca(u, v);diff_point[u] += val;diff_point[v] += val;diff_point[l] -= val;if (parent[l][0] != -1) {diff_point[parent[l][0]] -= val;}
}// 深度优先搜索,计算边差分的结果
void dfs_edge(int u, int p) {for (int v : adj[u]) {if (v != p) {dfs_edge(v, u);diff_edge[u] += diff_edge[v];}}
}// 深度优先搜索,计算点差分的结果
void dfs_point(int u, int p) {for (int v : adj[u]) {if (v != p) {dfs_point(v, u);diff_point[u] += diff_point[v];}}
}int main() {int n; // 节点数量cin >> n;for (int i = 0; i < n - 1; i++) {int u, v;cin >> u >> v;adj[u].push_back(v);adj[v].push_back(u);}// 初始化根节点的父节点为 -1dfs(1, -1, 0);int q; // 查询数量cin >> q;while (q--) {int u, v, val;cin >> u >> v >> val;// 边差分操作edge_diff(u, v, val);// 点差分操作point_diff(u, v, val);}// 计算边差分的结果dfs_edge(1, -1);// 计算点差分的结果dfs_point(1, -1);// 输出边差分的结果cout << "边差分结果:" << endl;for (int i = 1; i <= n; i++) {cout << "节点 " << i << " 关联边的修改值: " << diff_edge[i] << endl;}// 输出点差分的结果cout << "点差分结果:" << endl;for (int i = 1; i <= n; i++) {cout << "节点 " << i << " 的修改值: " << diff_point[i] << endl;}return 0;
}
100分代码:
#include <bits/stdc++.h>
using namespace std;const int N = 50005;
vector<int> g[N];
int depth[N], fa[N][20];
int b[N]; //树上差分数组,本质是对节点值的修改
int s[N]; //树上前缀和数组,本质对子树所有节点值修改的累加
int n, k, ans;void dfs(int u, int parent)
{depth[u] = depth[parent] + 1;fa[u][0] = parent;for(int j = 1; j <= 18; j++)fa[u][j] = fa[fa[u][j - 1]][j - 1];for(auto v : g[u]){if(v == parent) continue;dfs(v, u);}
}int lca(int u, int v)
{if(depth[u] < depth[v]) swap(u, v);for(int j = 18; j >= 0; j--){if(depth[fa[u][j]] >= depth[v])u = fa[u][j];}if(u == v) return u;for(int j = 18; j >= 0; j--){if(fa[u][j] != fa[v][j]){u = fa[u][j];v = fa[v][j];}}return fa[u][0];
}//树上前缀和,节点u的前缀和是节点u的值加上所有子树的前缀和
void sum(int u, int parent)
{s[u] = b[u]; for(auto v : g[u]){if(v == parent) continue;sum(v, u);s[u] += s[v];}ans = max(ans, s[u]);
}int main()
{cin >> n >> k;int u, v;for(int i = 1; i < n; i++){cin >> u >> v;g[u].push_back(v);g[v].push_back(u);}dfs(1, 0);while(k--){cin >> u >> v;int p = lca(u, v);b[u]++;b[v]++;b[p]--;b[fa[p][0]]--;}sum(1, 0);cout << ans;return 0;
}