1065 字
5 分钟
Luogu P3218 题解
Luogu P3218
题目大意:
求取被指定的多条路径覆盖的树中找出被覆盖次数最多的节点
!!INPUT:
-
第一行
N: 节点K: 路径数
-
接下来
N - 1行,每一行:xy: 节点x与y之间有通路
-
接下来
K行:st: 路径端点
思路
考虑使用链式前向星建树。
遍历路上的节点,经过一次加1。
题中路径已知两端点,记起点为 , 终点为 则路径为
若直接遍历,则时间复杂度为 ,按题目数据最多遍历 , 这是不能接受的。
考虑使用树上差分优化。
关于前缀和 & 差分的知识,可以去OI-WiKi中学习。
如果要对结点 和 之间的路径上的所有点权都加 ,可以对它的差分序列 做如下操作:
在所有修改操作完成后,可以计算一次子树和,就能得到更新后的点权。
倍增求LCA
首先第一遍DFS预处理数组,得到二维倍增数组 及 深度数组 depth。fa[u][i]表示节点i往上走步的父节点。
接下来进行LCA查询,具体步骤:跳、跳、跳……然后就到LCA了。
从最低节点出发,往上跳diff步即可。具体操作:
for (int i = 0; i <= LOG; i++) if (diff & (1 << i)) // 若 diff 的二进制第 i 位为 1 u = fa[u][i];再两个点从同一高度出发,一起蹦蹦跳跳直到两点相同。虽然使用while循环可以优化一丢丢,但是这里还是给出for循环的代码:
for (int i = 0; i <= LOG; i++) if (fa[u][i] != fa[v][i]) { u = fa[a][i]; v = fa[b][i]; }即为此时的的父节点即。
树上差分
树上差分和一维差分略有不同。树上差分是子节点**“在后续的累加过程中,要向它的父节点(以及自己)传递多少值”**。
例如对于树:
graph TD1/0 --> 2/01/0 --> 3/02/0 --> 4/02/0 --> 5/03/0 --> 6/03/0 --> 7/0要给路径加上 , 只需要给
如果 不是根节点,则 也需要自减。
因为在和自增的过程中,及其以上的所有节点的值都相应地减了两个,因此给及其父节点各自减一刚好可以抵消。
在最后结束时,使用前缀和合并差分数组即可得出初始值。
注意:需要使用后序遍历(即左->右->根的顺序),因为根节点需要统计所有子节点的贡献(直接在递归子节点后加上子节点的值即可)。
**提示:**在后序遍历之前记得先初始化节点值为差分值(因为也包括它自己的贡献)
示例代码
#include <bits/stdc++.h>using namespace std;
const int LOG = 20;const int MAXN = 5e4 + 10;
int fa[MAXN][LOG << 2], depth[MAXN], D[MAXN], val[MAXN];vector<int> tree[MAXN];
void dfs(int x, int father){ fa[x][0] = father; for (int i = 1; i <= LOG; i++) { fa[x][i] = fa[fa[x][i-1]][i-1]; // 一分为二 } for (auto child: tree[x]) { if (child == fa[x][0]) continue; depth[child] = depth[x] + 1; // 深度+1 dfs(child, x); }}
int lca(int u, int v){ if (depth[v] > depth[u]) swap(u, v); int diff = depth[u] - depth[v]; for (int i = 0; i <= LOG; i++) if (diff & (1 << i)) // 若 diff 的第 i 位为 1 u = fa[u][i]; if (u == v) return u; if (fa[u][0] == fa[v][0]) return fa[u][0]; for (int i = LOG; i >= 0; i--) if (fa[u][i] != fa[v][i]) { u = fa[u][i]; v = fa[v][i]; } return fa[u][0];}
void accumulate(int x, int father){ val[x] = D[x]; for (auto child: tree[x]) { if (child == father) continue; accumulate(child, x); val[x] += val[child]; }}
signed main(){ int n, k; cin >> n >> k; for (int i = 1; i < n; i++) { int u, v; cin >> u >> v; tree[u].push_back(v); tree[v].push_back(u); }
dfs(1, 0); // 根节点的爸爸是 0
for (int i = 1; i <= k; i++) { int s, t; cin >> s >> t;
int l = lca(s, t);
++D[s]; ++D[t]; --D[l];
if (fa[l][0]) --D[fa[l][0]]; }
accumulate(1, 0);
int ans = -0x3f3f3f3f; for (int i = 1; i <= n; i++) { ans = max(ans, val[i]); }
cout << ans << '\n';
return 0;} Luogu P3218 题解
https://www.zhx-blog.top/posts/2025-10-03-luogu_p3218题解/