1. 程式人生 > 其它 >題解 CF1065F Up and Down the Tree

題解 CF1065F Up and Down the Tree

這個 dp 我倒是真的一開始沒有思路。

有一棵樹,你從根出發,每次選擇一個子樹內的葉子到達,然後最多往上跳 \(k\) 步,繼續重複這個過程,最多到幾個點。

陰間模擬賽做到的題。
前兩題完全不會,看到就很崩潰。這題依然只想到一個暴力求 LCA 建邊以後強聯通分量縮點再跑拓撲的做法,而且還是 \(n^2\) 的。

正確的姿勢是,每棵子樹總有一個位置是要停下的。為了到其它的子樹去,這個位置一定是在最淺的那個葉子。注意,這是對於一堆葉子的 LCA 為根的子樹來說的,在一個點根的子樹中可能並步滿足這個“金科玉律”。
知道了這一點,就先把最小的葉子的深度記下來,記作 \(low_u\)

至於 dp,狀態當然是一個點為根的子樹中最多可能到的個數啦。
那麼考慮怎麼合併兩個答案。我們遍歷一個點的每一個出邊,找到它們的 \(low\)

,處理完下面的點,你應該是在這個深度上的,那麼就判斷這個深度和當前的深度是否不超過 \(k\),不超過的話就把下面的答案加到上面來。為避免重複,加上來後還得把下面的答案清空。
最後統計答案再 dfs 一遍,每個點把最大的兒子的答案加上當前的答案就可以了。

等等,這個做法看上去好像不太靠譜啊,怎麼只管了子樹裡最淺的兒子呢。
我剛剛看這個做法的時候也感覺不太對。但是仔細模擬一下這個過程就可以發現,我們其實是把每個葉子的答案加到了能通過上上下下一級一級往上跳到達的地方,這個狀態就代表了所有下面能到這個點的葉子數。
而列舉一個點的出邊把 \(low_v - dep_u \le k\) 的答案都加到這裡就相當於把互相能到達的加到了一起。所以這個做法是對的。

感覺這題還是很妙的!

#include <cstdio>
#include <vector>
const int N = 1000005, INF = 0x3f3f3f3f;
std::vector<int> g[N];
int n, k, size[N], low[N], dep[N];
bool leaf[N];
void dfs(int u, int d) {
    dep[u] = d, low[u] = INF;
    if (leaf[u]) low[u] = d, size[u] = 1;
    for (int i = 0; i < (signed)g[u].size(); i++) {
        int v = g[u][i];
        dfs(v, d+1);
        low[u] = std::min(low[u], low[v]);
        if (low[v] - dep[u] <= k) size[u] += size[v], size[v] = 0;
    }
}
int get(int u) {
    int an = 0;
    for (int i = 0; i < (signed)g[u].size(); i++) 
        an = std::max(an, get(g[u][i]));
    return an + size[u];
}
int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) leaf[i] = 1;
    for (int i = 2, x; i <= n; i++) {
        scanf("%d", &x);
        g[x].push_back(i), leaf[x] = 0;
    }
    dfs(1, 0);
    printf("%d", get(1));
    return 0;
}