Loading docs/graph/virtual-tree.md +36 −29 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy 对于这题来说,我们只需要保证红色的点无法到达 $1$ 号节点就行了。 通过肉眼观察可以得出结论—— $1$ 号节点的右子树(虽然实际上可能有多个子树,但这里只有两个子树,所以暂时这么称呼了)一个红色节点都木有, **所以没必要去 DP 它** 。 通过肉眼观察可以得出结论—— $1$ 号节点的右子树(虽然实际上可能有多个子树,但这里只有两个子树,所以暂时这么称呼了)一个红色节点都没有, **所以没必要去 DP 它** 。 观察题目给出的条件,红色点(关键点)的总数是与 $n$ 同阶的,也就是说实际上一次询问中红色的点对于整棵树来说是很稀疏的,所以如果我们能让复杂度由红色点的总数来决定就好了。 Loading Loading @@ -89,7 +89,7 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy 非常直观的一个方法是: - 将关键点按 DFS 序排序; - `for` 一遍,任意两个相邻的关键点求一下 LCA,并且哈希表判重; - 遍历一遍,任意两个相邻的关键点求一下 LCA,并且哈希表判重; - 然后根据原树中的祖先后代关系建树。 朴素算法的复杂度较高。因此我们提出一种单调栈做法。 Loading Loading @@ -154,7 +154,7 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy - 取序列第二个作为当前节点,为 $6$ 。再取栈顶元素,为 $4$ 。求 $6$ 和 $4$ 的 $LCA$ : $LCA(6,4)=1$ 。 - 发现 $LCA(6,4)\neq$ 栈顶元素,进入判断阶段。 - 判断阶段:发现栈顶节点 $4$ 的 DFS 序是大于 $LCA(6,4)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是等于 $LCA$ 的(其实 DFS 序相等说明节点也相等),说明 $LCA$ 已经入栈了,所以直接连接 $1->4$ 的边,也就是 $LCA$ 到栈顶元素的边。并把 $4$ 从栈中弹出。 - 判断阶段:发现栈顶节点 $4$ 的 DFS 序是大于 $LCA(6,4)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是等于 $LCA$ 的(其实 DFS 序相等说明节点也相等),说明 $LCA$ 已经入栈了,所以直接连接 $1\to4$ 的边,也就是 $LCA$ 到栈顶元素的边。并把 $4$ 从栈中弹出。  Loading @@ -164,13 +164,13 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy - 取序列第三个作为当前节点,为 $7$ 。再取栈顶元素,为 $6$ 。求 $7$ 和 $6$ 的 $LCA$ : $LCA(7,6)=3$ 。 - 发现 $LCA(7,6)\neq$ 栈顶元素,进入判断阶段。 - 判断阶段:发现栈顶节点 $6$ 的 DFS 序是大于 $LCA(7,6)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是小于 $LCA$ 的,说明 $LCA$ 还没有入过栈,所以直接连接 $3->6$ 的边,也就是 $LCA$ 到栈顶元素的边。把 $6$ 从栈中弹出,并且把 $LCA(6,7)$ 入栈。 - 判断阶段:发现栈顶节点 $6$ 的 DFS 序是大于 $LCA(7,6)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是小于 $LCA$ 的,说明 $LCA$ 还没有入过栈,所以直接连接 $3\to6$ 的边,也就是 $LCA$ 到栈顶元素的边。把 $6$ 从栈中弹出,并且把 $LCA(6,7)$ 入栈。 - 结束了判断阶段,将 $7$ 入栈,当前栈为 $1,3,7$ 。  - 发现序列里的 3 个节点已经全部加入过栈了,退出循环。 - 此时栈中还有 3 个节点: $1, 3,7$ ,很明显它们是一条链上的,所以直接链接: $1->3$ 和 $3->7$ 的边。 - 此时栈中还有 3 个节点: $1,3,7$ ,很明显它们是一条链上的,所以直接链接: $1\to3$ 和 $3\to7$ 的边。 - 虚树就建完啦!  Loading @@ -185,12 +185,17 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy ???+note "代码实现" ```cpp sort(h + 1, h + 1 + k, cmp); inline bool cmp(const int x, const int y) { return id[x] < id[y]; } void build() { sort(h + 1, h + k + 1, cmp); sta[top = 1] = 1, g.sz = 0, g.head[1] = -1; // 1 号节点入栈,清空 1 号节点对应的邻接表,设置邻接表边数为 1 for (int i = 1, l; i <= k; i += 1) if (h[i] != 1) { // 如果1号节点是关键节点就不要重复添加 l = lca(h[i], sta[top]); // 计算当前节点与栈顶节点的LCA for (int i = 1, l; i <= k; ++i) if (h[i] != 1) { //如果 1 号节点是关键节点就不要重复添加 l = lca(h[i], sta[top]); //计算当前节点与栈顶节点的 LCA if (l != sta[top]) { //如果 LCA 和栈顶元素不同,则说明当前节点不再当前栈所存的链上 while (id[l] < id[sta[top - 1]]) Loading @@ -208,8 +213,10 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy g.head[h[i]] = -1, sta[++top] = h[i]; //当前节点必然是第一次入栈,清空邻接表并入栈 } for (int i = 1; i < top; i += 1) for (int i = 1; i < top; ++i) g.push(sta[i], sta[i + 1]); //剩余的最后一条链连接一下 return; } ``` 于是我们就学会了虚树的建立了! Loading Loading
docs/graph/virtual-tree.md +36 −29 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy 对于这题来说,我们只需要保证红色的点无法到达 $1$ 号节点就行了。 通过肉眼观察可以得出结论—— $1$ 号节点的右子树(虽然实际上可能有多个子树,但这里只有两个子树,所以暂时这么称呼了)一个红色节点都木有, **所以没必要去 DP 它** 。 通过肉眼观察可以得出结论—— $1$ 号节点的右子树(虽然实际上可能有多个子树,但这里只有两个子树,所以暂时这么称呼了)一个红色节点都没有, **所以没必要去 DP 它** 。 观察题目给出的条件,红色点(关键点)的总数是与 $n$ 同阶的,也就是说实际上一次询问中红色的点对于整棵树来说是很稀疏的,所以如果我们能让复杂度由红色点的总数来决定就好了。 Loading Loading @@ -89,7 +89,7 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy 非常直观的一个方法是: - 将关键点按 DFS 序排序; - `for` 一遍,任意两个相邻的关键点求一下 LCA,并且哈希表判重; - 遍历一遍,任意两个相邻的关键点求一下 LCA,并且哈希表判重; - 然后根据原树中的祖先后代关系建树。 朴素算法的复杂度较高。因此我们提出一种单调栈做法。 Loading Loading @@ -154,7 +154,7 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy - 取序列第二个作为当前节点,为 $6$ 。再取栈顶元素,为 $4$ 。求 $6$ 和 $4$ 的 $LCA$ : $LCA(6,4)=1$ 。 - 发现 $LCA(6,4)\neq$ 栈顶元素,进入判断阶段。 - 判断阶段:发现栈顶节点 $4$ 的 DFS 序是大于 $LCA(6,4)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是等于 $LCA$ 的(其实 DFS 序相等说明节点也相等),说明 $LCA$ 已经入栈了,所以直接连接 $1->4$ 的边,也就是 $LCA$ 到栈顶元素的边。并把 $4$ 从栈中弹出。 - 判断阶段:发现栈顶节点 $4$ 的 DFS 序是大于 $LCA(6,4)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是等于 $LCA$ 的(其实 DFS 序相等说明节点也相等),说明 $LCA$ 已经入栈了,所以直接连接 $1\to4$ 的边,也就是 $LCA$ 到栈顶元素的边。并把 $4$ 从栈中弹出。  Loading @@ -164,13 +164,13 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy - 取序列第三个作为当前节点,为 $7$ 。再取栈顶元素,为 $6$ 。求 $7$ 和 $6$ 的 $LCA$ : $LCA(7,6)=3$ 。 - 发现 $LCA(7,6)\neq$ 栈顶元素,进入判断阶段。 - 判断阶段:发现栈顶节点 $6$ 的 DFS 序是大于 $LCA(7,6)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是小于 $LCA$ 的,说明 $LCA$ 还没有入过栈,所以直接连接 $3->6$ 的边,也就是 $LCA$ 到栈顶元素的边。把 $6$ 从栈中弹出,并且把 $LCA(6,7)$ 入栈。 - 判断阶段:发现栈顶节点 $6$ 的 DFS 序是大于 $LCA(7,6)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是小于 $LCA$ 的,说明 $LCA$ 还没有入过栈,所以直接连接 $3\to6$ 的边,也就是 $LCA$ 到栈顶元素的边。把 $6$ 从栈中弹出,并且把 $LCA(6,7)$ 入栈。 - 结束了判断阶段,将 $7$ 入栈,当前栈为 $1,3,7$ 。  - 发现序列里的 3 个节点已经全部加入过栈了,退出循环。 - 此时栈中还有 3 个节点: $1, 3,7$ ,很明显它们是一条链上的,所以直接链接: $1->3$ 和 $3->7$ 的边。 - 此时栈中还有 3 个节点: $1,3,7$ ,很明显它们是一条链上的,所以直接链接: $1\to3$ 和 $3\to7$ 的边。 - 虚树就建完啦!  Loading @@ -185,12 +185,17 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy ???+note "代码实现" ```cpp sort(h + 1, h + 1 + k, cmp); inline bool cmp(const int x, const int y) { return id[x] < id[y]; } void build() { sort(h + 1, h + k + 1, cmp); sta[top = 1] = 1, g.sz = 0, g.head[1] = -1; // 1 号节点入栈,清空 1 号节点对应的邻接表,设置邻接表边数为 1 for (int i = 1, l; i <= k; i += 1) if (h[i] != 1) { // 如果1号节点是关键节点就不要重复添加 l = lca(h[i], sta[top]); // 计算当前节点与栈顶节点的LCA for (int i = 1, l; i <= k; ++i) if (h[i] != 1) { //如果 1 号节点是关键节点就不要重复添加 l = lca(h[i], sta[top]); //计算当前节点与栈顶节点的 LCA if (l != sta[top]) { //如果 LCA 和栈顶元素不同,则说明当前节点不再当前栈所存的链上 while (id[l] < id[sta[top - 1]]) Loading @@ -208,8 +213,10 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy g.head[h[i]] = -1, sta[++top] = h[i]; //当前节点必然是第一次入栈,清空邻接表并入栈 } for (int i = 1; i < top; i += 1) for (int i = 1; i < top; ++i) g.push(sta[i], sta[i + 1]); //剩余的最后一条链连接一下 return; } ``` 于是我们就学会了虚树的建立了! Loading