【学习笔记】树链剖分系列二——LCA

没想到吧,今天还有时间,我还能更!

其实是打摆多了连自己都觉得不合适了,又不想做题,只能水水博客了。

书接上回:【学习笔记】树链剖分系列1——树链剖分(重链剖分)——我是不是应该写一篇树链剖分呢

上回中,我埋了个伏笔:

是不是有点像倍增LCA。

并且我在下方给出了LCA 的题:

求LCA
P4281 [AHOI2008]紧急集合 / 聚会
P1967 [NOIP2013 提高组] 货车运输
P4427 [BJOI2018]求和

所以,回收伏笔,来看看树剖怎么求LCA。

前置知识:

还是那一大坨定义:

重儿子:对于每一个非叶子节点,它的儿子中,子树最大的儿子,为该节点的重儿子。

轻儿子:对于每一个非叶子节点,它的儿子中,除重儿子外的所有儿子即为轻儿子。

重边:连接任意两个重儿子的边叫做重边。

轻边:除重边以外的所有边。

重链:相邻重边连起来的,一条连接重儿子的链叫重链。

轻链:相邻轻边连起来的,一条连接轻儿子的链叫轻链。

思想

树剖的核心在于跳链,这和倍增不谋而合,所以,LCA的树剖求法类似倍增。

先进行两边 dfs,维护出那些数组。

设两个节点,xy

每次让 deep[top[x]]deep[top[y]] 大的在下边,然后让它往上跳。

跳到最后,xy 肯定在一条重链上了,这时候深度小的节点就是节点 xy 的LCA。

int Get_Lca(int x, int y){
while(top[x] != top[y]){
if(deep[top[x]] < deep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(deep[x] > deep[y]) swap(x, y);
return x;
}

就这么简单。其实跟下一篇还有点联系来着。

我们接下来来看题:

P4281 [AHOI2008]紧急集合 / 聚会

给出三个点,找出一个到三个点花费的总和最小的点,以及最小的花费。

让集合点最优,一定在这三个点相互通达的路径上。多画画图,感觉就出来了(我博客里懒得画了)。

然后,会发现,三个点如果两两求LCA,会有两个LCA相同,这时的最优集合点一定是那个不同的LCA。

花费可以树上差分,就是 deep[x]+deep[y]+deep[z]deep[lca1]deep[lca2]deep[lca3]

Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 5e5 + 10;
int n, m, cnt, num;
int head[MAXN];
int fa[MAXN], son[MAXN], deep[MAXN], size[MAXN];
int dfn[MAXN], top[MAXN];
struct Edge{
int to, next;
}e[MAXN << 1];
inline void Add(int u, int v){
e[++cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
void dfs_deep(int rt, int father, int depth){
size[rt] = 1;
fa[rt] = father;
deep[rt] = depth;
int max_son = -1;
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(v == father) continue;
dfs_deep(v, rt, depth + 1);
size[rt] += size[v];
if(size[v] > max_son){
son[rt] = v;
max_son = size[v];
}
}
}
void dfs_top(int rt, int top_fa){
dfn[rt] = ++num;
top[rt] = top_fa;
if(!son[rt]) return;
dfs_top(son[rt], top_fa);
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(!dfn[v]) dfs_top(v, v);
}
}
int Get_Lca(int x, int y){
while(top[x] != top[y]){
if(deep[top[x]] < deep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(deep[x] > deep[y]) swap(x, y);
return x;
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main(){
n = read(), m = read();
for(register int i = 1; i <= n - 1; i++){
int u, v;
u = read(), v = read();
Add(u, v);
Add(v, u);
}
dfs_deep(1, 0, 1);
dfs_top(1, 1);
for(register int i = 1; i <= m; i++){
int x, y, z;
x = read(), y = read(), z = read();
int anc_final;
int anc1 = Get_Lca(x, y);
int anc2 = Get_Lca(x, z);
int anc3 = Get_Lca(y, z);
int dis = deep[x] + deep[y] + deep[z] - deep[anc1] - deep[anc2] - deep[anc3];
if(anc1 == anc2) anc_final = anc3;
else if(anc1 == anc3) anc_final = anc2;
else if(anc2 == anc3) anc_final = anc1;
printf("%d %d\n", anc_final, dis);
}
return 0;
}

P4427 [BJOI2018]求和

这一眼直接LCA + 树上差分。

可以观察到,k<=50n<=3e5,并不大。
一棵树的平均深度是 logn,退化成链的最坏深度是 n,所以我们可以预处理出 1~n1~k 次幂,同时维护一个 1 ~ k 次幂的树上前缀和。

最后的答案就是

sum[u][k]+sum[v][k]2sum[lca][k]+val[lca][k]

记得取模后被减数可能小于减数,所以减完要先加上模数再取模,考试的时候因为这个 100 pts →
0 pts。

Code

#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const int MAXN = 3e5 + 10, MAXK = 55;
const int Mod = 998244353;
int n, m, cnt, num;
int head[MAXN];
int fa[MAXN], son[MAXN], size[MAXN], deep[MAXN];
int dfn[MAXN], top[MAXN];
LL p[MAXN][MAXK], sum[MAXN][MAXK];
struct Edge{
int to, next;
}e[MAXN << 1];
inline void Add(int u, int v){
e[++cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
void dfs_deep(int rt, int father, int depth){
size[rt] = 1;
fa[rt] = father;
deep[rt] = depth;
for(register int i = 1; i <= 50; i++)
sum[rt][i] = 1LL * (sum[fa[rt]][i] + p[deep[rt]][i]) % Mod;
int max_son = -1;
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(v == father) continue;
dfs_deep(v, rt, depth + 1);
size[rt] += size[v];
if(max_son < size[v]){
son[rt] = v;
max_son = size[v];
}
}
}
void dfs_top(int rt, int top_fa){
dfn[rt] = ++num;
top[rt] = top_fa;
if(!son[rt]) return;
dfs_top(son[rt], top_fa);
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(!dfn[v]) dfs_top(v, v);
}
}
int Get_Lca(int x, int y){
while(top[x] != top[y]){
if(deep[top[x]] < deep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(deep[x] > deep[y]) swap(x, y);
return x;
}
LL Get_Sum(int x, int y, int k){
int lca = Get_Lca(x, y);
return (1LL * (sum[x][k] + sum[y][k]) % Mod - (2 * sum[lca][k] % Mod) + Mod + p[deep[lca]][k]) % Mod;
}
void init(){
for(register int i = 1; i <= n; i++){
p[i][0] = 1;
for(register int j = 1; j <= 50; j++)
p[i][j] = 1LL * p[i][j - 1] * i % Mod;
}
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main(){
n = read();
init();
for(register int i = 1; i <= n - 1; i++){
int u, v;
u = read(), v = read();
Add(u, v);
Add(v, u);
}
dfs_deep(1, 0, 0);
dfs_top(1, 1);
m = read();
for(register int i = 1; i <= m; i++){
int x, y, k;
x = read(), y = read(), k = read();
printf("%lld\n", 1LL * Get_Sum(x, y, k));
}
return 0;
}
posted @   TSTYFST  阅读(72)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示