【学习笔记】树链剖分系列二——LCA
没想到吧,今天还有时间,我还能更!
其实是打摆多了连自己都觉得不合适了,又不想做题,只能水水博客了。
书接上回:【学习笔记】树链剖分系列1——树链剖分(重链剖分)——我是不是应该写一篇树链剖分呢
上回中,我埋了个伏笔:
是不是有点像倍增LCA。
并且我在下方给出了LCA 的题:
求LCA
P4281 [AHOI2008]紧急集合 / 聚会
P1967 [NOIP2013 提高组] 货车运输
P4427 [BJOI2018]求和
所以,回收伏笔,来看看树剖怎么求LCA。
前置知识:
还是那一大坨定义:
重儿子:对于每一个非叶子节点,它的儿子中,子树最大的儿子,为该节点的重儿子。
轻儿子:对于每一个非叶子节点,它的儿子中,除重儿子外的所有儿子即为轻儿子。
重边:连接任意两个重儿子的边叫做重边。
轻边:除重边以外的所有边。
重链:相邻重边连起来的,一条连接重儿子的链叫重链。
轻链:相邻轻边连起来的,一条连接轻儿子的链叫轻链。
思想
树剖的核心在于跳链,这和倍增不谋而合,所以,LCA的树剖求法类似倍增。
先进行两边 ,维护出那些数组。
设两个节点,,。
每次让 与 大的在下边,然后让它往上跳。
跳到最后,, 肯定在一条重链上了,这时候深度小的节点就是节点 , 的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。
花费可以树上差分,就是 。
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 + 树上差分。
可以观察到,,,并不大。
一棵树的平均深度是 ,退化成链的最坏深度是 ,所以我们可以预处理出 ~ 的 ~ 次幂,同时维护一个 ~ 次幂的树上前缀和。
最后的答案就是
记得取模后被减数可能小于减数,所以减完要先加上模数再取模,考试的时候因为这个 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; }
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16596841.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理