总结
1)求树上两点距离
dis (a , b) = depth[a] + depth[b] - 2 * depth[ LCA (a, b) ]
2)求线段重合距离
A到C和B到C的重合长度 = (dis_AC + dis_BC - dis_AB)/ 2
LCA Tarjan:
理解:离线算法,建好树后再查询,一次DFS 吧所有查询解决完。
时间复杂度:O(n+q);
n个点 q次询问
补一下:链式向前星,并查集 ,Tarjan
代码
#include<iostream>
#include<math.h>
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN = 5e5+ 10;
int fa[MAXN], head[MAXN], head_ask[MAXN], cnt, cnt_ask, ans[MAXN];
bool vis[MAXN];
int n, m, s;
struct Edge{
int to, dis, next;
int num;
}edge[MAXN << 1], ask[MAXN << 1];
void add_edge(int u, int v, int dis) {
edge[++cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
void add_ask(int x, int y, int num) { //num 第几个查询
ask[++cnt_ask].to = y;
ask[cnt_ask].num = num; //第几个查询
ask[cnt_ask].next = head_ask[x];
head_ask[x] = cnt_ask;
}
int find(int x) { //并查集
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void init() {
cnt = 1;
cnt_ask = 1;
memset(vis, 0, sizeof(vis));
fa[n] = n;
}
void Tarjan(int x) {
vis[x] = true;
for(int i = head[x]; i ; i = edge[i].next) {
int to = edge[i].to;
if( !vis[to] ) {
Tarjan(to);
fa[to] = x;
}
}
for(int i = head_ask[x]; i; i = ask[i].next) {
int to = ask[i].to;
if( vis[to] ){
ans[ask[i].num] = find(to);
}
}
}
int main () {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m >> s;
int x, y;
init();
for(int i = 1; i < n; ++i) {
fa[i] = i;
cin >> x >> y;
add_edge(x, y, 0);
add_edge(y, x, 0);
}
for(int i = 1; i <= m; ++i) {
cin >> x >> y;
add_ask(x, y, i);
add_ask(y, x, i);
}
Tarjan(s);
for(int i = 1; i <= m; ++i) {
cout << ans[i] << endl;
}
}
练习题:
LCA 倍增:
补习顺序:
1)白话理解
2)初始代码
3)优化:
1.链式向前星存图
2. 把a, b 两个节点抬升到同一高度,用二进制思想。
其他 :链式向前星
我对倍增的理解:
1)初次了解LCA(最近公共祖先)我只能想到在树上的两点,同时一步一步往上跳,直到相遇就是两个点的LCA。
2)然后学习了倍增优化,每次跳 步,不再一步一步跳,这样我们可以节省大量的时间 ( 树越深体现越深 )
1. fa [ i ][ j ] 表示 结点 i 向上 层的祖先
2. fa [ i ][ 0 ] 表示 结点 i 向上 = 1 层的祖先,即 i 的父亲节点, (fa[ i ][ 0 ] 就是递推的基础条件)
3. fa[ i ][ j ] = fa[ fa [ i ][ j - 1] ] [j -1] 对这个式子的理解 :fa[ i ][j - 1]是结点 i 往上跳层的祖先,那我们就在跳到这个结点的基础上,再向上跳层,这样就相当于从结点 i,先跳层,再跳层,最后还是到达了 层.
4. 如果用链式向前星存图时,在对树的深搜时我们就可以保存每个结点的 第 步跳到的父亲结点。
//更新每个节点的深度, 在搜索过程中
void DFS (int now, int pre) {
depth[now] = depth[pre] + 1;
fa[now][0] = pre;
for (int i = 1; (1 << i) <= depth[now]; ++i) {
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
for(int i = head[now]; i; i = edge[i].next) {
if(edge[i].to != pre) {
DFS(edge[i].to, now);
}
}
}
LCA部分
int LCA(int a, int b) {
//让 a 处于更低的深度
if (depth[a] < depth[b]) swap(a, b);
//然后让 a 向上爬,爬到与b相同深度
int dep = depth[a] - depth[b];
for (int i = 0; (1 << i) <= dep; ++i) {
if ((1 << i) & dep) {
a = fa[a][i];
}
}
//如果 b 爬到与 a 同一深度时 a == b 则直接返回该节点(该节点就是a b的LCA)
if(a == b) return a;
//否者 a b 同时向上倍增 从最远的开始试
for (int i = log2(depth[a]); i >= 0; --i) {
//如果父亲不同就向上跳, 如果父亲相同就减小距离再比较,直到不相同在跳。
if (fa[a][i] != fa[b][i]) {
a = fa[a][i];
b = fa[b][i];
}
}
return fa[a][0];
//跳到最后 a 和 b 距离最近的LCA只差一步 所以返回 dp[a][0] 即可
}
整体模板
const int MAXN = ;
int head[MAXN], cnt;
int fa[MAXN][20], depth[MAXN];
struct Edge{
int to, dis, next;
}edge[MAXN << 1];
void add_edge(int u, int v, int dis) {
edge[++cnt].to = v;
edge[cnt].dis = 0;
edge[cnt].next = head[u];
head[u] = cnt;
}
void init() {
cnt = 1;
memset(head, 0, sizeof(head));
memset(depth, 0 , sizeof(depth));
memset(fa, 0, sizeof(fa));
}
//更新每个节点的深度, 在搜索过程中
void DFS (int now, int pre) {
depth[now] = depth[pre] + 1;
fa[now][0] = pre;
for (int i = 1; (1 << i) <= depth[now]; ++i) {
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
for(int i = head[now]; i; i = edge[i].next) {
if(edge[i].to != pre) {
DFS(edge[i].to, now);
}
}
}
int LCA(int a, int b) {
//让 a 处于更低的深度
if (depth[a] < depth[b]) swap(a, b);
//然后让 a 向上爬,爬到与b相同深度
int dep = depth[a] - depth[b];
for (int i = 0; (1 << i) <= dep; ++i) {
if ((1 << i) & dep) {
a = fa[a][i];
}
}
//如果 b 爬到与 a 同一深度时 a == b 则直接返回该节点(该节点就是a b的LCA)
if(a == b) return a;
//否者 a b 同时向上倍增 从最远的开始试
for (int i = log2(depth[a]); i >= 0; --i) {
//如果父亲不同就向上跳, 如果父亲相同就减小距离再比较,直到不相同在跳。
if (fa[a][i] != fa[b][i]) {
a = fa[a][i];
b = fa[b][i];
}
}
return fa[a][0];
//跳到最后 a 和 b 距离最近的LCA只差一步 所以返回 dp[a][0] 即可
}
DFS(S, 0);
LCA(x, y);