Contest7519 - 虚树计算

Contest

A 消耗战(弱化版)

题意:只有一组询问的消耗战(B 题)。

这个题跟虚树没有半点关系。只是为 B 题做准备。

\(f_u\) 为切断 \(u\) 与其子树内所有关键点的最小代价(不需要考虑 \(u\) 是关键点的情况)。答案为 \(f_1\)

\(mi_u\)\(1 \rightsquigarrow u\) 的最小边权(特别地,\(mi_1 = \infty\))。一遍 DFS 预处理。

枚举 \(u\) 的儿子 \(v\)

  • \(v\) 是关键点,则 \(ans \gets ans + mi_v\)
  • \(v\) 不是关键点,则 \(ans \gets ans + f_v\)

还有一种可能:直接切 \(mi_u\) 会更好。所以答案为 \(\min(ans, mi_u)\)

时间复杂度 \(\Theta(n)\)

B 消耗战

P2495 [SDOI2011] 消耗战

虚树是什么

给定树上的若干个关键点 \(S\)。往 \(S\) 添加最少的点,使得 \(S\) 对 LCA 运算封闭

把新的点集叫做 \(S'\)。如果把 \(S'\) 中的点按照原树的祖先后代关系连边,则可以形成一棵小树。这棵树被称为 \(S\) 对应的虚树

可以证明,\(|S| \le |S'| \le 2 |S| - 1\),即 \(|S'| = \Theta(|S|)\)

因此 \(\sum |S'| = \Theta(\sum |S|)\)。在类似本题(限制关键点数目之和)的题目当中,保证了时间复杂度。

虚树的构造方法

虚树的构造方法有两种:单调栈和二次排序。其中单调栈用的似乎比较多。但是二次排序好写好背,所以下文介绍二次排序。

  1. 预处理 \(dfn\) 和 LCA。
  2. \(S\)\(dfn\) 排序。
  3. 枚举 \(S\) 的每对相邻点对(\(S_{i-1}\)\(S_i\)),将 \(\text{LCA}(S_{i-1}, S_i)\) 加入 \(S'\)。当然 \(S\) 中的所有元素也要加入。
  4. \(S'\)\(dfn\) 排序,并去重。此时 \(S'\) 已经构造完毕
  5. 新建一个空图 \(g\)。枚举 \(S'\) 的每对相邻点对(\(S'_{i-1}\)\(S'_i\)),在 \(g\) 上连边 \(\text{LCA}(S'_{i-1}, S'_i) \rightarrow S'_i\)虚树即为 \(g\)

时间复杂度 \(O(|S| \log |S|)\)

(备注:单调栈做法的好处在于,在点集已经排序的情况下,借助 \(O(1)\) 查询的 LCA 可以做到 \(\Theta(|S|)\) 构造虚树)

虚树的构造:正确性

Part 1:为什么这样构造的 \(S'\) 对 LCA 封闭

  • 引理 1 \(\text{LCA}(S_{i-1}, S_{i+1}) \in \{\text{LCA}(S_{i-1}, S_i), \text{LCA}(S_i, S_{i+1})\}\)

证明:\(\text{LCA}(S_{i-1}, S_i)\)\(\text{LCA}(S_i, S_{i+1})\) 是祖先后代关系,因为它们都是 \(S_2\) 的祖先。那么他俩中 dep 小的那个就是 \(\text{LCA}(S_{i-1}, S_{i+1})\)\(\square\)

  • 引理 2 \(\text{LCA}(S_i, S_j) \in \{\text{LCA}(S_{k-1}, S_k) \mid i < k \le j\}\)。即 \(\text{LCA}(S_i, S_j) \in S'\)

证明:用引理 1 数学归纳法即可。\(\square\)

  • 引理 3 \(\operatorname{LCA}\limits_{u \in T} u\)\(T\)\(dfn\) 最小和最大两个点的 LCA。

(这个好像是个常用结论,但是我不会)

证明:先证明这个点确实是所有点的 CA,然后证明它是 LCA。

\(dfn\) 最小和最大的点为 \(a,b\)

反证:设 \(dfn_a < dfn_c < dfn_b\),且 \(\text{LCA}(a,b)\) 不是 \(c\) 的祖先。根据 \(dfn\) 的性质,\(c\) 不在 \(\text{LCA}(a,b)\) 的子树内,因此 \(dfn_c < dfn_a\)\(dfn_c > dfn_b\)。矛盾。因此这个点确实是所有点的 CA。

如果这个点不是 LCA(即还有点比它更深),那么和它是 \(\text{LCA}(a,b)\) 这一点矛盾。

\(\square\)

  • 定理 \(S'\)\(\text{LCA}\) 封闭。

证明:对于 \(S'\) 中任意两个元素,他们的 LCA 必定是 \(S\) 的一个子集的 LCA。通过引理 3 可以转化为 \(S\) 的一个点对的 LCA,通过引理 2 可知必定在 \(S'\) 中。\(\square\)

Part 2:为什么对 \(S'\) 这样连边是正确的

根据 \(S'\)\(\text{LCA}\) 的封闭性,\(\text{LCA}(S'_{i-1}, S'_i)\)\(S'\) 中的元素,且确实是 \(S_i'\) 的祖先。

因为 \(S'_{i-1}\)\(S'_i\)\(dfn\) 排序后是连续的,所以 \(\text{LCA}(S'_{i-1}, S'_i) \rightsquigarrow S'_i\) 之间没别的 \(S'\) 中的点了,确实就是 \(\text{LCA}(S'_{i-1}, S'_i)\) 应该连 \(S'_i\)

虚树的构造:代码

T 即为上文的 \(S'\)

vector<int> g[MAXN];
vector<int> build_virtual_tree(vector<int> S) {
    sort(S.begin(), S.end(), [&](int u, int v) {
        return dfn[u] < dfn[v];
    });
    vector<int> T;
    T.push_back(S[0]);
    for (int i = 1; i < S.size(); i++) {
        T.push_back(LCA(S[i-1], S[i]));
        T.push_back(S[i]);
    }
    sort(T.begin(), T.end(), [&](int u, int v) {
        return dfn[u] < dfn[v];
    });
    T.erase(unique(T.begin(), T.end()), T.end());
    for (int i = 1; i < T.size(); i++) {
        int u = LCA(T[i-1], T[i]), v = T[i];
        g[u].push_back(v);
    }
    return T;
}

虚树的小结论

\(S = \{S_0, S_1, \cdots, S_{m-1}\}\)(按 \(dfn\) 排序)的虚树边权和为:

\[\frac 1 2 \sum\limits_{i=0}^{m-1} \text{dis}(S_i, S_{(i+1) \bmod m}) \]

例题:P3320 [SDOI2015] 寻宝游戏 能看出 SDOI 挺喜欢虚树的

C 世界树

P3233 [HNOI2014] 世界树

TODO:

posted @ 2024-12-09 22:30  August_Light  阅读(19)  评论(0)    收藏  举报