【動態規劃】樹形DP完全詳解!

RioTian發表於2021-08-19

蒟蒻大佬時隔三個月更新了!!拍手拍手

而且是更新了幾篇關於DP的文章(RioTian狂喜)

現在趕緊複習一波樹形DP....

樹形DP基礎:Here,CF上部分樹形DP練習題:Here


\[QAQ \]


在學習樹形DP之前,我們先要搞清楚一個問題,什麼是樹?根據圖論課上學到的知識我們知道,連通的無圈圖稱為樹。而樹我們可以把它近似第看成一個分形結構,這是說我們的樹其實是可以遞迴定義的,樹的每個子樹也是一顆完整的樹,而這種結構就天然地適合遞迴。

具體來說,在樹形動態規劃當中,我們一般先運算元樹再進行合併,在實現上與樹的後序遍歷相似,都是先遍歷子樹,遍歷完之後將子樹的值傳給父親。簡單來說我們動態規劃的過程大概就是先遞迴訪問所有子樹,再在根上合併。

瞭解了樹形動態規劃的基本思想後,做一些經典的樹形DP題型

【經典例題】

【子樹大小】

Description

給你一棵有 $n$ 個點的樹($1$ 號點為根節點),求以 $i$ 為根的子樹的大小。


這道題作為我們樹形動態規劃的引例,當然是非常簡單的,只要跑一遍 \(DFS\) 即可求出。

具體細節:

  • \(f_i\)\(i\) 為根的子樹大小,則 \(f[i] = \sum f[k] + 1\) ,其中 \(k\)\(i\) 子節點。

    如果寫成偽碼,大概長這個樣子

void dfs(u){
	if (u 是葉子) f[u] = 1 return
    for (v 是 u 的兒子){
        dfs(v)
        f[u] += f[v]
    }
    f[u] += 1 // 本身
}

這就是最簡單的樹形DP了,有沒有感受到先遍歷子樹,遍歷完之後將子樹的值傳給父親這樣的動態規劃過程呢?

【樹的平衡點】

Description

給你一個有 $n$ 個點的樹,求樹的平衡點和刪除平衡點後最大子樹的節點數。所謂平衡點,指的是樹中的一個點,刪掉該點,使剩下的若干個連通塊中,最大的連通塊的塊大小最少。


要解決這個問題,我們首先還是先確定狀態,現在的問題還很簡單,一般就是題目問什麼我們就設什麼,所以我們先設 \(F[i]\)​​ 為刪除 \(i\)​​ 號點後最大連通塊的塊大小。然後我們沿用上一題的設 \(f[i]\)​​ 為以 \(i\)​​ 為根的子樹大小,那麼很簡單就有 \(F[i]=max(n−f[i],max(f[k]))\)​​,其中 \(k\)​​ 是 \(i\)​ 的子節點。這是因為,刪除 \(i\)​ 號點後,我們剩下的連通塊要麼是 \(i\) 的子樹,要麼是 \(i\)​ 的父親所在的連通塊。

通俗的來講,即刪除某個節點後,它的兒子就成了獨立的連通塊,那麼最大連通塊就是 max(x 的所有兒子連通塊最大的 size,n - f[x])

借用一下蒟蒻dalao的圖

樹的平衡點示意圖

所以我們只需要先沿用第一題的方法先算出每個點的子樹大小,再 \(DFS\) 一遍算出每個點的 \(F[i]\)​ ,其值最小的點就是我們樹的平衡點啦。

【程式碼實現】

 vector<int>e[N];
int ans, idx, f[N];
void dfs(int u, int fa) {
    f[u] = 1;
    int mx = 0;
    for (int v : e[u]) {
        if (v == fa)continue;
        dfs(v, u);
        f[u] += f[v];
        mx = max(mx, f[v]);
    }
    mx = max(mx, n - f[u]);
    if (ans > mx) ans = mx, idx = u;
}

沒有上司的舞會 (樹的最大獨立集)

Description

有 $n$ 名職員,編號為 $1\sim n$ ,他們的關係就像一棵以老闆為根的樹,父節點就是子節點的直接上司。每個職員有一個快樂指數,用整數 $H_i$ 給出,現在要召開一場週年慶宴會,不過,沒有職員願意和直接上司一起參會。在滿足這個條件的前提下,主辦方希望邀請一部分職員參會,使得所有參會職員的快樂指數總和最大,求這個最大值。


這個在 樹形DP基礎 裡講過了

我們把題意抽象一下,沒有職員會和上司一同參會,也就是說在這棵樹上不存在任何一條邊使得連線的兩個點都來參會,換句話說這道題其實要我們求的是 樹的最大權值的獨立集

蒟蒻大佬在他的部落格提到了——— 黑白染色,也就是在樹上一層選一層不選這樣的貪心。

但其實很容易證明簡單的黑白染色是不對的,如下圖所示,左邊是黑白染色的結果,右邊是正解的結果。其中黑點表示要來參加的人,白點表示不來參加的人,在這個例子中我們預設每個人的快樂指數都是一樣的。

樹的最大獨立集示意圖

所以我們只好老老實實地去確定狀態一步一步來。我們發現 \(i\)​ 號點選或者不選其實是會影響子樹的結果的,我們要在狀態中將這一層影響交代清楚。所以我們用 \(f[i][0]\)​ 表示不選 \(i\)​ 點去參加舞會時 \(i\)​ 點及其子樹所能選的最大的快樂指數; \(f[i][1]\) 表示選 \(i\) 點去參加舞會時 \(i\) 點及其子樹所能選的最大的快樂指數。然後我們考慮怎麼轉移,當不選 \(i\)​ 點去參加舞會時,它的兒子們可以參加也可以不參加,所以有如下的轉移方程

轉移方程:\(f[i][0] = \sum(max(f[k][0],f[k][1]))\)​​ ,其中 \(k\)\(i\)​ 的兒子;

  • 當選 \(i\) 去參加舞會時,它的兒子不能參加,所以 \(f[i][0] = \sum f[k][0] + H_i\)

其中我們的邊界條件就是當 \(i\) 是葉子時 \(f[i][0] = 0,f[i][1] = H_i\)

我們最後的答案就是 \(ans = max(f[root][0],f[root][1])\)

而如果轉變為樹的最大獨立集就只要讓我們的 $ H_i = 1$​ 對所有點成立就可以了。

【AC Code】

const int N = 1e4 + 10;
vector tr[N];
int f[N][2], v[N], Happy[N], n;
void dfs(int u) {
    f[u][0] = 0; f[u][1] = Happy[u];
    for (auto v : tr[u]) {
        dfs(v);
        f[u][0] += max(f[v][0], f[v][1]);
        f[u][1] += f[v][0];
    }
}
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> Happy[i];
    for (int i = 1, x, y; i < n; ++i) {
        cin >> x >> y;
        v[x] = 1;// x has a father
        tr[y].push_back(x);
    }
    int root;
    for (int i = 1; i <= n; ++i)
        if (!v[i]) {root = i; break;}
    dfs(root);
    cout << max(f[root][0], f[root][1]) << "\n";
}

Strategic game (樹的最小點覆蓋)

Description

給你一個有 $n$ 個點的樹,每兩個點之間至多隻有一條邊。如果在一個結點上放一個士兵,那他能看守與之相連的邊,問最少放多少個兵,才能把所有的邊能看守住。


樹的最小點覆蓋,問題示意圖

這道題和上一道題: 沒有上司的舞會 (樹的最大獨立集) 挺像的,題目連結:Here

都是能夠發現 \(i\) 號點選或不選是會影響子樹的結果。但和上一題兒子父親不能同時選不一樣的是:兒子父親不能同時選不一樣。

那我們的狀態就很明朗了,

  • \(f[i][0]\) 為不在 \(i\) 號點上放士兵並且以 \(i\) 為根的每條邊都被看住的最小士兵數;
  • \(f[i][1]\) 為在 \(i\) 號點上放士兵並且以 \(i\)​ 為根的子樹的每條邊都被看住的最小士兵數。

接下來我們來考慮轉移,

  • \(i\)​ 點不放士兵時,它的兒子就必須都要放士兵,所以 \(f[i][0] = \sum f[k][1]\)​ ;

  • \(i\) 點不放士兵時,它的兒子可以放士兵,也可以不放士兵,所以

    \(f[i][1] = 1 + \sum(min(f[k][0],f[k][1]))\)

我們最後的答案就是:\(ans = min(f[root][0],f[root][1])\)

這就是樹的最小點覆蓋問題的解法。

vector<int>e[N];
int f[N][2];
bool st[N];
void dfs(int u) {
    st[u] = 1;
    f[u][0] = 0, f[u][1] = 1;
    for (int i = 0; i < e[u].size(); ++i) {
        int v = e[u][i];
        if (st[v]) continue;
        dfs(v);
        f[u][0] += f[v][1];
        f[u][1] += min(f[v][0], f[v][1]);
    }
}
int main() {
    int n, m, k, x;
    while (~scanf("%d", &n)) {
        for (int i = 1; i <= n; ++i) {
            scanf("%d:(%d)", &m, &k);
            while (k--) {
                scanf("%d", &x);
                e[m].push_back(x), e[x].push_back(m);
            }
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            if (st[i]) continue;
            dfs(i);
            ans += min(f[i][0], f[i][1]);
        }
        printf("%d\n", ans);
        memset(st, 0, sizeof(st));
        for (int i = 0; i < N; ++i) e[i].clear();
    }
}

Cell Phone Network (樹的最小支配集)

題目連結:Here

Description

給你一個有 $n$ 個點的樹,每兩個點之間至多隻有一條邊。如果在第 $i$ 個點部署訊號塔,就可以讓它和所有與它相連的點都收到訊號。求最少部署多少個訊號塔能讓所有點都能收到訊號。


Cell Phone Network

這道題可以說是上兩道題的升級版了,之前兩道題一個點選或者不選,只會影響它的兒子選或者不選;但是在這道題當中,一個點選或者不選不僅會影響兒子還會影響父親,所以在狀態設計上會更加複雜一點點。

  • \(f[i][0]\) 表示選點 \(i\),且以 \(i\) 為根的子樹每個點都被覆蓋的最少訊號塔部署數量;

  • \(f[i][1]\) 表示不選點 \(i\),並且 \(i\) 被兒子覆蓋的最少訊號塔部署數量;

  • \(f[i][2]\) 為不選 \(i\),但是 \(i\) 沒被兒子覆蓋,且以 \(i\)​ 為根的子樹的其他點都被覆蓋的最少訊號塔部署數量,

    換句話說這時 \(i\) 的父親一定要選來覆蓋一下 \(i\)

下面我們來看看如何進行轉移。

  • 當我們選 \(i\) 點時,\(i\) 點的兒子可選可不選,並且可以已經被覆蓋和未被覆蓋,所以我們有

    \(f[i][0] = 1 + \sum(min(f[k][0],f[k][1],f[k][2]))\)

  • 當我們不選點 \(i\),並且 \(i\) 未被覆蓋的時候,它的兒子們都至少被覆蓋或者不選,不能不選又不被覆蓋,所以這個時候

    \(f[i][2] = \sum(f[k][1],f[k][0])\)

最難的情況是當我們不選點 \(i\),並且 \(i\) 被覆蓋的時候,這個情況是最複雜的情況,也就是這時 \(i\) 的所有兒子至少有一個被選,也就是至少有一個 \(f[k][0]\),這時我們分情況討論:

  1. 如果這時 \(i\) 存在一個兒子 \(k\) ,有 \(f[k][0] \le f[k][1]\) ,也就說選了它不會更劣,那麼我們就選它,我們的答案迴歸 \(f[i][1] = \sum(f[k][1],f[k][0])\)
  2. 如果不存在這樣一個兒子,也就是說對於所有兒子,選了都會變得更劣,那我們就要記錄一下 \(minn = min(f[k][0] ,f[k][1])\) , 也就是選一個損失最小的來選,我們的答案就變成 \(f[i][1] = \sum(f[k][1],f[k][0]) + minn\) .

這題有點複雜我下面展示一下大致的參考程式碼:

void dfs(int x){
    v[x] = 1;
    f[x][0] = 1,f[x][1] = f[x][2] = 0;
    int tmp = inf;
    bool flag = 1;
    for(int i = 0;i < e[x].size();++i){
        int y = e[x][i];
        if(vis[y]) continue;
        dfs(y);
        f[x][2] += min(f[y][1],f[y][0]);
        f[x][0] += min(f[y][0],f[y][1],f[y][2]); // 過載min
        if(f[y][0] <= f[y][1]){
            flag = 0;
            f[x][1] += f[y][0];
        } else{
            f[x][1] += f[y][1];
            tmp = min(tmp,f[y][0] - f[y][1]);
        }
    }
    if(flag) f[x][1] += tmp;
}

// 簡化思路的AC程式碼
const int N = 1e4 + 10, inf = 0x3f3f3f3f;
vector<int>e[N];
int ans;
bool vis[N];

void dfs(int u, int fa) {
    bool f = 0;
    for (int i = 0; i < e[u].size(); ++i) {
        int v = e[u][i];
        if (v == fa) continue;
        dfs(v, u); f |= vis[v];
    }
    if (!f && !vis[u] && !vis[fa])
        vis[fa] = 1, ans += 1;
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n; cin >> n;
    for (int i = 1, x, y; i <= n; ++i) {
        cin >> x >> y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1, 0);
    cout << ans;
}

到這裡我們的最小支配集問題也得到了解決。

【二叉蘋果樹】樹上揹包問題

Description

有一棵二叉蘋果樹,如果數字有分叉,一定是分兩叉,即沒有隻有一個兒子的節點。這棵樹共 $N$ 個節點,標號 $1$ 至 $N$,樹根編號一定為 。我們用一根樹枝兩端連線的節點編號描述一根樹枝的位置,比如下圖是一棵有四根樹枝的蘋果樹。因為樹枝太多了,需要剪枝。但是一些樹枝上長有蘋果,給定需要保留的樹枝數量,求最多能留住多少蘋果。


二叉蘋果樹-樣例圖

在剛學樹形DP的時候遇到過,當時沒能解決

題目連結:Here

首先這道題規定蘋果是長在樹枝上的,所以我們不妨設

  • \(a[i].l\) 表示連線 \(i\) 和其左兒子樹枝上的蘋果數量
  • \(a[i].r\)表示連線 \(i\)​ 和其右兒子樹枝上的蘋果數量

我們在這裡由於最後要求在給定需要保留的樹枝數量時最多能留住蘋果的數量,所以我們不妨直接設 \(f[i][j]\) 表示以 \(i\) 為根的子樹保留 \(j\) 個樹枝時可以得到的最大的蘋果數量。接下來我們就來考慮一下如何轉移,我們的轉移大致分為四種情況:

  • 首先是對於 \(i\),它的左右子樹都保留的情況,這時以 \(i\) 為根的子樹保留 \(j\) 個樹枝,我們再設左右子樹分別保留 \(x\)\(y\) 個樹枝,那麼我們有 \(x+y+2=j\),所以我們就列舉 x,這時 \(y=j−2−x\),所以這時

    \(f[i][j] = \max\limits_{x\in[0,j-2]}(f[l][x] + f[r][j - 2-x] + a[j].l + a[j].r)\)

    情況一

  • 接著是對於只保留左子樹的情況,這時以 \(i\) 為根的子樹保留 \(j\) 個樹枝,所以左子樹要保留 \(j−1\) 個樹枝,所以這時 \(f[i][j]=f[l][j−1]+a[j].l\)

情況二

  • 其次是對於只保留右子樹的情況,這時以 \(i\) 為根的子樹保留 \(j\) 個樹枝,所以右子樹也要保留 \(j−1\) 個樹枝,所以這時 \(f[i][j]=f[r][j−1]+a[j].r\)

情況三

  • 最後是左右子樹都不保留的情況,這時 \(j=0\),也就是 \(f[i][0]=0\)

這樣分析完 \(4\) 種情況之後是不是感覺很簡單呢?這時我們升級一下這道題,假如這棵樹不是二叉樹了,是多叉樹的話我們要怎麼辦?我們假設這時是 \(k\) 叉樹,這時最簡單的做法就是列舉 \(k−1\) 叉的數量出來,剩下一個通過總和為 \(j\) 算出來進行轉移。但是這樣時間開銷非常大,肯定是會 TLE 的。所以這時我們就要用到樹上揹包的思想。

我們先開一個虛構的左子樹,如下圖三角形,然後我們讓第一個子樹當作右子樹和我們虛構的左子樹合併,並將合併完的結果當成一個左子樹再喝第二個子樹合併,並將合併完的結果當作一個左子樹和第三個子樹合併……以此類推。而其中的合併就是我們上面二叉樹列舉 \(x\) 的過程。也就是說在這個過程中我們不斷將子樹合併進入我們的左子樹當中,強行當成二叉樹進行操作。

具體寫法的轉移過程大概是這樣的:

\(f[u][j] = max(f[u][k] + f[v][j - k - 1] + a[u][v])\) 其中 \(f[u][j]\) 表示的是以 i 為根的子樹保留 j 個樹枝時可以得到的最大的蘋果數量,\(f[u][k]\) 就是我們的“左子樹”保留 \(k\) 個樹枝時可以得到的最大的蘋果數量,\(v\)\(u\) 的兒子,所以 \(f[v][j−k−1]\) 就是我們當前的“右兒子”保留 \(j−k−1\) 個樹枝時可以得到的最大的蘋果數量, \(a[u][k]\) 就是我們邊上的蘋果數量。

這其實就是一個樹上揹包的思想,希望同學們能夠用心體會一下。

【樹的直徑】

另一篇關於樹的直徑的文章詳細見這裡:Here,練習:Here

Description

給你一個有 $n$ 個點的樹,樹上的每個點有一個權值。定義一棵樹的子鏈大小為:這個子鏈上所有節點的權值和。求這棵樹上最大子鏈的結點權值和。


原題連結:Here

搜尋基礎好的同學可能已經看出來了,其實不用 DP ,兩遍 DFS 也能解決問題。具體來說,我們從樹上任意一個點以起點出發,找到一條最長的邊然後以這條邊的另一個頂點作為起點再 DFS 一遍找到的最長邊就是我們樹的直徑。

樹的直徑示意圖1

那一遍 DFS ,從一個點出發找一條第一長的找一條第二長的加起來行不行呢?那肯定是不行的,還是這個例子,我們選一個第一長的一個第二長的,就發現我們經過了一條公共邊,這當然是不允許的。

樹的直徑示意圖2

那這個兩遍 DFS 為什麼是對的呢,我們下面來感性證明一下。

首先我們先抽線一下我們的過程,我們就是先從一點出發找到離他最遠的另一點,再從找到的那一點出發找一條從它出發的最長的鏈。

樹的直徑示意圖3

首先如果我們第一步找到的點在我們的最長鏈上,那答案一定是對的。下面我們證明我們第一步找到的點一定在我們的最長鏈上,若不然,我們分兩種情況進行討論:

  • 首先是真實的最長鏈和我們求出的最長鏈有交點,並相交在第一步求出的最長鏈上的情況。假如最長鏈如下圖藍色所示,假若它長於我們找到的黑色。首先由於我們第一遍找的是根開始的最長鏈,所以 \(a≥c\),而第二遍我們是從找到的那一點出發找的最長鏈,所以 \(b≥d\),所以 \(a+b≥c+d\),這與假設的藍色才是最長鏈 \(a+b<c+d\) 矛盾!

樹的直徑示意圖4

  • 接著是真實的最長鏈和我們求出的最長鏈有交點,並相交在第二步求出的最長鏈上的情況。假如最長鏈如下圖藍色所示,假若它長於我們找到的黑色。首先由於我們第一遍找的是根開始的最長鏈,所以 \(a≥c+t≥c\)​,而第二遍我們是從找到的那一點出發找的最長鏈,所以 \(b≥d+t≥d\)​,所以 \(a+b≥c+d\)​,這與假設的藍色才是最長鏈 \(a+b<c+d\) 矛盾!

樹的直徑示意圖5

  • 最後就是真實的最長鏈和我們求出的最長鏈沒有交點的情況。假如最長鏈如下圖藍色所示,假若它長於我們找到的黑色。不失一般性,我們不妨設 \(d≥c\),這時我們另一條鏈也就是我們橙色這條的長度為 \(L=a+x+y+d\) ,由於我們的 \(a+x\) 是從根出發的最長鏈,所以 \(a+x≥y+d\),而 \(d≥c\),所以

    \(L = a + x + y + d \ge y + d+ y+d \ge 2y + c + d > c + d\)

    這與 \(c+d\)​​ 是最長鏈相矛盾!從另一個角度我們也能分析出矛盾,我們知道 \(a+x\)​​ 是從根出發的最長鏈,所以 \(a≥b\)​,而 \(c+d>a+b\)​,而不妨設 \(d≥c\)​,此時至少有 \(d>b\)​,所以 \(x+y+d>b\),所以 \(L=a+x+y+d>a+b\) ,這與 \(a+b\) 是從我們第一次搜尋找到的點出發的最長鏈相矛盾!

樹的直徑示意圖6

綜上所述兩遍 DFS 的方法的正確性是無可否認的。

搞定了 DFS 後,接下來我們考慮我們如何使用樹形動態規劃的方法來解決這個問題。首先我們知道我們最後的答案一定是如我們那個抽象的圖一樣是從一個低點上升到最高點再下降的,而上升下降兩條邊一定是這個最高點的兩條最長邊。我們由此得到啟示,我們設 \(f[i]\)​​​​ 表示 \(i\)​​​​ 號點向子樹的最長鏈的長度,我們一遍 DFS 進行 DP,我們先遞迴處理 \(i\)​​​​ 的所有兒子 \(k\)​​​​,然後用 \(max(f[k])+a[i]\)​​​ 來更新 \(f[i]\)​​​,其中 \(a[i]\)​​​ 是 \(i\)​​​ 號點的權值。然後我們用最大的和次大的 \(f[k^′]\)​​ 和 \(f[k^{″}]\)​ 來更新答案 \(ans=max(ans,f[k^′]+f[k^{″}]+a[i])\)。這樣我們就能求出我們樹的直徑了。

當然其實不是點權而是邊權的情況也能輕鬆完成,畢竟點權和邊權是可以通過點邊轉換來互相轉換的。

Rinne Loves Edges

Description

$Rinne$ 最近了解了如何快速維護可支援插入邊刪除邊的圖,並且高效的回答一下奇妙的詢問。 她現在拿到了一個 $n$ 個節點 $m(m=n-1)$ 條邊的無向連通圖,每條邊有一個邊權 現在她想玩一個遊戲:選取一個 “重要點” $S$ ,然後選擇性刪除一些邊,使得原圖中所有除 $S$ 之外度為 $1$ 的點都不能到達 $S$。
定義刪除一條邊的代價為這條邊的邊權,現在 $Rinne$ 告訴你他選取的“重要點” 是誰,她想知道完成這個遊戲的最小的代價,這樣她就能輕鬆到達 $rk1$ 了!作為回報,她會讓你的排名上升一定的數量。


4月份做每日一題時寫的題解:Here

首先我們發現這是一個 \(n\)​ 個節點 \(n−1\)​ 條邊的無向連通圖,所以也就是一棵樹。由於重要點已知,所以我們我們的問題就變成了我們設重要點為根,我們要刪除最小權值的一系列邊,使得根與每一個葉子都不連通。如果考慮吧在 \(i\)​ 這個點的子樹上實現每個葉子都和 \(S\)​ 不連通,那麼有兩種情況,一是直接把 \(i\) 和它的兒子斷開;而是在 \(i\) 的子樹上以及實現了所有葉子節點無法通到 \(i\) .

所以我們就可以順勢設出 \(f[i]\)​ 表示以 \(i\)​ 為根的子樹上所有的葉子都和根斷開的最小代價,所以 \(f[i]=∑(min(f[k],dis[i][k]))\),其中 \(k\)\(i\) 的兒子,\(dis[i][k]\)\(i\)\(k\) 的邊權。

這道題到這也輕鬆的解決了。我們下面來考慮一下這道題的一個變型,下面是題目描述:

Description

$Rinne$ 最近了解了如何快速維護可支援插入邊刪除邊的圖,並且高效的回答一下奇妙的詢問。 她現在拿到了一個 $n(1\le n\le 1000)$ 個節點 $m(m=n-1)$ 條邊的無向連通圖,每條邊有一個邊權 $w_i$ 現在她想玩一個遊戲:選取一個 “重要點” $S$,然後選擇性刪除一些邊,使得原圖中所有除 $S$ 之外度為 $1$ 的點都不能到達 $S$。
定義刪除一條邊的代價為這條邊的邊權,由於能力有限,切斷邊的總代價不能超過 $m$,且要讓切斷邊中最大的代價最小。問在能力有限的條件下,切斷邊中最大的代價的最小值是多少?


首先有一個很容易想到的錯誤做法,就是我們把邊進行排序,然後從小的開始刪。但因為有總長度限制,這種刪法又可能會刪除一些多餘的邊,比如斷開一個子樹時,這個子樹裡面已經有被我刪的邊了,就可能達不到它能力 \(m\) 的限制,所以是不太對的。

一看到最大值最小,是不是 DNA 就動起來了,沒錯就是二分的思想。我們去二分能切的最長的邊的代價,這時我們就把我們的邊分成了能切斷的和不能切斷的。這時我們跑上面的那個動態規劃,假設這時二分到 \(x\)​​​​ 我們設 \(f[i]\)​​​ 表示以 \(i\)​​ 為根的子樹上所有的葉子都和根斷開的最小代價,所以當 \(k\)​​ 是 \(i\)​ 的兒子時,若 \(dis[i][k]≤x\)​,也就是這條邊時可以切斷的時候,\(f[i]+=min(f[k],dis[i][k])\),反之只能 \(f[i]+=f[k]\) 將斷開的任務交給下面了。

我們最後看看答案 \(ans\) 是否滿足 \(ans≤m\)​ 來判斷我們的二分怎麼移動就可以完成這道題了。


\[QAQ \]


最後還有兩道題分別是:HDU 3586POJ 2152

這裡稍微提一下思路,

HDU 3586:最小化最大值(二分),對上限進行二分,用樹形DP去判斷;

POJ 2152

參考文章

相關文章