虚树复习 & O(1) 查询 LCA
放假是不可能做题的。
那就写总结把。
反正快退役了,能写一点是一点吧。
虚树
问题的情境涉及多次树上询问,每次指定一些点,让你计算。
此类问题需要我们在线地找到尽可能少的【关键点】进行计算,最好和给的点级别一样。
虚树的思想就是这个过程。
二次排序
一个关键直觉:【指定点】两两的 LCA 一定是【关键点】。
并且,LCA 就是关键点,不多不少。(corner case:将 1 强制加入,保证联通)
快速求 LCA 就要把这些【指定点】按 dfn 排序,然后两两求 LCA。
如何给这些点加边使得形成虚树?
再排一次序,每次将 \(lca(x,y)\) 向 \(y\) 连边即可。
void VTree() {
sort(a + 1, a + len + 1, cmp);
for(int i = 2;i <= k + 1; ++i) a[++len] = Lca(a[i - 1], a[i]);
sort(a + 1, a + len + 1, cmp);
int m = unique(a + 1, a + len + 1) - a - 1; len = m;
for(int i = 2;i <= len; ++i) { // 将 lca(x, y) 向 y 连边
int x = Lca(a[i], a[i - 1]), y = a[i];
add_edge(x, y);
}
Dfs_Dp(1, 0);
}
while(m--) {
scanf("%lld", &k); a[len = 1] = 1;
for(int i = 2;i <= k + 1; ++i) scanf("%lld", &a[i]), vis[a[i]] = 1;
len = k + 1;
VTree(); ans = dp[1];
printf("%lld\n", ans);
for(int i = 1;i <= len; ++i) g[a[i]].clear(), dp[a[i]] = inf, vis[a[i]] = 0;
}
单调栈
不太想学。
看了一下大致的思路类似于笛卡尔树(是维护的右链),这里的单调栈全程也是一条链。
每次将栈顶元素和新加入的点求 LCA,根据【是否 LCA = 栈顶】进行讨论。
记一下这个思想就行了。实现还是考虑二次排序。
例题
鸽。没心情写题解。
O(1) 求 LCA
上面虚树构建中要求 LCA,为了减小常数,所以希望 LCA 是 \(O(1)\) 的。
欧拉序列
注意!!!不是即这棵树的括号序列。
而是:递归 \(x\) 开始时,将 \(x\) 加入,每次递归完子树后,回溯时再将 \(x\) 加入。
每个点 1 次,每条边 1 次,所以欧拉序列的长度为 \(n+(n-1)=2n-1\)。
定义【欧拉序】表示一个点在欧拉序列中第一次出现的位置 \(pos(x)\)。
欧拉序有以下两条性质:
-
任意两点简单路径上所有节点均在它们的欧拉序之间出现。
-
任意两点欧拉序之间不会出现它们 LCA 子树外的点。
然后 st 表查询区间里面深度最小值对应的点即可。预处理线性对数,查询常数。