Contest7519 - 虛樹計算

August_Light發表於2024-12-09

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\)

虛樹的構造:程式碼

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:

相關文章