洛谷题单指南-图论之树-P3128 [USACO15DEC] Max Flow P
原题链接: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);
}
// 初始化根节点的父节点为 -1
dfs(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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
2024-03-06 洛谷题单指南-搜索-P1605 迷宫
2024-03-06 洛谷题单指南-搜索-P1433 吃奶酪
2024-03-06 洛谷题单指南-搜索-P2036 [COCI 2008/2009 #2] PERKET