虚树复习 & O(1) 查询 LCA

放假是不可能做题的。

那就写总结把。

反正快退役了,能写一点是一点吧。

推荐:https://www.cnblogs.com/alex-wei/p/Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree.html

虚树

问题的情境涉及多次树上询问,每次指定一些点,让你计算。

此类问题需要我们在线地找到尽可能少的【关键点】进行计算,最好和给的点级别一样。

虚树的思想就是这个过程。

二次排序

一个关键直觉:【指定点】两两的 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 表查询区间里面深度最小值对应的点即可。预处理线性对数,查询常数。

posted @ 2024-07-15 17:47  LCat90  阅读(6)  评论(0编辑  收藏  举报