Unverified Commit d3ab07b5 authored by mgt/Enter-tainer's avatar mgt/Enter-tainer Committed by GitHub
Browse files

Merge pull request #2628 from OI-wiki/Early0v0-patch-1

parents 3e1600de 656f2cf5
Loading
Loading
Loading
Loading
+36 −29
Original line number Diff line number Diff line
@@ -58,7 +58,7 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy

对于这题来说,我们只需要保证红色的点无法到达 $1$ 号节点就行了。

通过肉眼观察可以得出结论—— $1$ 号节点的右子树(虽然实际上可能有多个子树,但这里只有两个子树,所以暂时这么称呼了)一个红色节点都有, **所以没必要去 DP 它**
通过肉眼观察可以得出结论—— $1$ 号节点的右子树(虽然实际上可能有多个子树,但这里只有两个子树,所以暂时这么称呼了)一个红色节点都有, **所以没必要去 DP 它**

观察题目给出的条件,红色点(关键点)的总数是与 $n$ 同阶的,也就是说实际上一次询问中红色的点对于整棵树来说是很稀疏的,所以如果我们能让复杂度由红色点的总数来决定就好了。

@@ -89,7 +89,7 @@ author: HeRaNO, Ir1d, konnyakuxzy, ksyx, Xeonacid, konnyakuxzy, greyqz, sshwy
非常直观的一个方法是:

- 将关键点按 DFS 序排序;
-  `for` 一遍,任意两个相邻的关键点求一下 LCA,并且哈希表判重;
- 遍历一遍,任意两个相邻的关键点求一下 LCA,并且哈希表判重;
- 然后根据原树中的祖先后代关系建树。

朴素算法的复杂度较高。因此我们提出一种单调栈做法。
@@ -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$ 从栈中弹出。

![vtree-15](./images/vtree-15.png)

@@ -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$ 。

![vtree-17](./images/vtree-17.png)

- 发现序列里的 3 个节点已经全部加入过栈了,退出循环。
- 此时栈中还有 3 个节点: $1, 3,7$ ,很明显它们是一条链上的,所以直接链接: $1->3$ 和 $3->7$ 的边。
- 此时栈中还有 3 个节点: $1,3,7$ ,很明显它们是一条链上的,所以直接链接: $1\to3$ 和 $3\to7$ 的边。
- 虚树就建完啦!

![vtree-18](./images/vtree-18.png)
@@ -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]])
@@ -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;
    }
    ```

于是我们就学会了虚树的建立了!