对于多次查询结点 × 和 $y4 的公共祖先.
a. 如果 x 的深度 <y 的深度,则交换 xy ,使得 x 更深;
b.采用二进制拆分的思想,根据两个结点的深度差,让 x 快速跳转到和 y 一样深;
c. 如果此时 x==y,可得 LCA
d. 如果不相等,采用二进制拆分的思想,让 xy 倍增上跳,直到 x 的父和 y 的父相遇
(因为跳出根以上,f[x][k]=0,因此如果让 x 和 y 相遇,可能结果为 0 ) ,可得 LCA ;
备注:单次查询的时间复杂度为 O(log2N)。
(2) 查询步骤:
a. x y两个结点中较深的点,倍增向上跳到两个结点深度相同;
b. 若相遇,得 LCA ,否则 x,y 同时向上倍增上跳直到 xy 的父相遇.
A. 树的公共祖先(LCA)(3)
题目描述
给定一棵 n 个结点的树(结点标号 1…n )以及树中结点的边,结点 s 为树的根。
有 m 次询问,请求出每次询问的两个结点 x 和 y 的最近的公共祖先结点。
输入
第 1 行输入 3 个整数 n、m、s(n≤500000,m≤500000,1≤s≤n);
接下来 n−1 行,每行两个整数 a 和 b ,结点 a 和 b 是父子关系,但不保证 a 是b 的父,数据保证一定能构成树;
#include<bits/stdc++.h> usingnamespace std; constint N = 1e5+20, L = 20; structnode { int to, nxt; } a[N * 2]; int n, m, fa[N][L], pre[N], k, dep[N], lg[N]; voidadd(int u, int v){ a[++k] = {v, pre[u]}; pre[u] = k; } voiddfs(int x, int fath){ dep[x] = dep[fath] + 1; fa[x][0] = fath; for (int i = pre[x]; i; i = a[i].nxt) { int to = a[i].to; if (to != fath) { dfs(to, x); } } } intLCA(int u, int v){ if (dep[u] < dep[v]) swap(u, v); //u永远>=v while (dep[u] > dep[v]) { u = fa[u][lg[dep[u] - dep[v]]]; // 宗旨:要让大的序号变成小的序号 } if (u == v) return u; for (int i = L - 1; i >= 0; i--) { if (fa[u][i] != fa[v][i]) { u = fa[u][i]; v = fa[v][i]; } } return fa[u][0];//返回父节点 } intmain(){ scanf("%d", &n); for (int i = 1; i < n; i++) { int x, y; scanf("%d%d", &x, &y); add(x, y); add(y, x); } dfs(1, 0); for (int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1; for (int i = 1; i < L; i++) { for (int j = 1; j <= n; j++) { fa[j][i] = fa[fa[j][i - 1]][i - 1]; } } scanf("%d", &m); while (m--) { int x, y; scanf("%d%d", &x, &y); int lc = LCA(x, y); // cout << "LCA: " << lc << endl; printf("%d\n",dep[x] - dep[lc] + dep[y] - dep[lc]); } }
C. P5836 [USACO19DEC] Milk Visits S
题目描述
Farmer John 计划建造 N 个农场,用 N−1 条道路连接,构成一棵树(也就是说,所有农场之间都互相可以到达,并且没有环)。每个农场有一头奶牛,品种为更赛牛或荷斯坦牛之一。
Farmer John 的 M 个朋友经常前来拜访他。在朋友 i 拜访之时,Farmer John 会与他的朋友沿着从农场 Ai 到农场 Bi 之间的唯一路径行走(可能有 Ai=Bi)。除此之外,他们还可以品尝他们经过的路径上任意一头奶牛的牛奶。由于 Farmer John 的朋友们大多数也是农场主,他们对牛奶有着极强的偏好。他的有些朋友只喝更赛牛的牛奶,其余的只喝荷斯坦牛的牛奶。任何 Farmer John 的朋友只有在他们访问时能喝到他们偏好的牛奶才会高兴。
请求出每个朋友在拜访过后是否会高兴。
输入格式
输入的第一行包含两个整数 N 和 M。
第二行包含一个长为 N 的字符串。如果第 i 个农场中的奶牛是更赛牛,则字符串中第 i 个字符为 G,如果第 i 个农场中的奶牛是荷斯坦牛则为 H。
以下 N−1 行,每行包含两个不同的整数 X 和 Y(1≤X,Y≤N),表示农场 X 与 Y 之间有一条道路。
以下 M 行,每行包含整数 Ai,Bi,以及一个字符 Ci。Ai 和 Bi 表示朋友 i 拜访时行走的路径的端点,Ci 是 G 或 H 之一,表示第 i 个朋友喜欢更赛牛的牛奶或是荷斯坦牛的牛奶。
输出格式
输出一个长为 M 的二进制字符串。如果第 i 个朋友会感到高兴,则字符串的第 i 个字符为 1,否则为 0。
输入输出样例 #1
输入 #1
1 2 3 4 5 6 7 8 9 10 11
5 5 HHGHG 1 2 2 3 2 4 1 5 1 4 H 1 4 G 1 3 G 1 3 H 5 5 H
我们在 dfs 时记录一下当前结点到根结点所经过的路线的GH数量,然后根据前缀和公式直接求解. xy 之间的 H 的数量 =x 到根结点的 H 的数量(算上起点和终点) +y 到根结点的 H 的数量(算上起点和终点) − $2\times $ 他们的公共祖先到根结点的 H 的数量(算上起点和终点)+公共祖先是否为 H (是=1,否=0).
#include<bits/stdc++.h> usingnamespace std; constint N = 1e6 + 20, L = 18; int lg[N], pre[N], fa[N][L], dep[N], b[N]; int n, m, x, y, z, k = 0;
structnode { int to, nxt, fr; } a[N * 2];
voidadd(int u, int v){ a[++k] = {v, pre[u], u}; pre[u] = k; }
voiddfs(int x, int fath){ dep[x] = dep[fath] + 1; fa[x][0] = fath; for (int i = pre[x]; i; i = a[i].nxt) { int to = a[i].to; if (to != fath) { dfs(to, x); } } }
boolcmp(int i, int j){ return i > j; }
intlca(int u, int v){ if (dep[u] < dep[v]) swap(u, v); while (dep[u] != dep[v]) { u = fa[u][lg[dep[u] - dep[v]]]; } if (u == v) return u; for (int i = L - 1; i >= 0; i--) { if (fa[u][i] != fa[v][i]) { u = fa[u][i]; v = fa[v][i]; } } return fa[u][0]; }
intmain(){ scanf("%d", &n); for (int i = 1; i < n; i++) { scanf("%d", &x); add(i, x); add(x, i); } dfs(0, 0); for (int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1; for (int i = 1; i < L; i++) { for (int j = 1; j <= n; j++) { fa[j][i] = fa[fa[j][i - 1]][i - 1]; } } scanf("%d", &m); while (m--) { scanf("%d", &x); for (int i = 1; i <= x; i++) scanf("%d", &b[i]); sort(b + 1, b + 1 + x, cmp); int l = b[1]; for (int i = 2; i <= x; i++) { l = lca(l, b[i]); } int ma = l; while (l != 0) { for (int i = pre[l]; i; i = a[i].nxt) { if (dep[a[i].to] < dep[l]) { ma = max(a[i].to, ma); l = a[i].to; goto endd; } } endd:; } printf("%d\n", ma);