【学习笔记】树链剖分系列二——LCA
没想到吧,今天还有时间,我还能更!
其实是打摆多了连自己都觉得不合适了,又不想做题,只能水水博客了。
书接上回:【学习笔记】树链剖分系列1——树链剖分(重链剖分)——我是不是应该写一篇树链剖分呢
上回中,我埋了个伏笔:
是不是有点像倍增LCA。
并且我在下方给出了LCA 的题:
求LCA
P4281 [AHOI2008]紧急集合 / 聚会
P1967 [NOIP2013 提高组] 货车运输
P4427 [BJOI2018]求和
所以,回收伏笔,来看看树剖怎么求LCA。
前置知识:
还是那一大坨定义:
重儿子:对于每一个非叶子节点,它的儿子中,子树最大的儿子,为该节点的重儿子。
轻儿子:对于每一个非叶子节点,它的儿子中,除重儿子外的所有儿子即为轻儿子。
重边:连接任意两个重儿子的边叫做重边。
轻边:除重边以外的所有边。
重链:相邻重边连起来的,一条连接重儿子的链叫重链。
轻链:相邻轻边连起来的,一条连接轻儿子的链叫轻链。
思想
树剖的核心在于跳链,这和倍增不谋而合,所以,LCA的树剖求法类似倍增。
先进行两边 \(dfs\),维护出那些数组。
设两个节点,\(x\),\(y\)。
每次让 \(deep[top[x]]\) 与 \(deep[top[y]]\) 大的在下边,然后让它往上跳。
跳到最后,\(x\),\(y\) 肯定在一条重链上了,这时候深度小的节点就是节点 \(x\),\(y\) 的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 <= 50\),\(n <= 3e5\),并不大。
一棵树的平均深度是 \(\log n\),退化成链的最坏深度是 \(n\),所以我们可以预处理出 \(1\)~\(n\) 的 \(1\)~\(k\) 次幂,同时维护一个 \(1\) ~ \(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;
}
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16596841.html