1. 程式人生 > 其它 >Codeforces Gym101667 A. Broadcast Stations 樹形DP

Codeforces Gym101667 A. Broadcast Stations 樹形DP

Codeforces Gym101667 A. Broadcast Stations

題意:

給定一棵樹,可以選定一些點在上面放基站。

若節點\(u\)上的基站(如果有的話)的輻射範圍為\(d\),那麼距離\(u\)小於等於\(d\)的點會被覆蓋。放一個輻射範圍為\(d\)的基站需要花費\(d\)

問:使得整棵樹被覆蓋的最小花費。

分析:

嘗試進行動態規劃。

我們很自然地考慮以\(u\)為根的樹被完全覆蓋的情況,設\(u\)有子節點\(v_1,v_2,\dots,v_m\)。如果令\(f(u)\)為以\(u\)為根節點的樹被覆蓋的最小花費,就需要考慮\(f(u)\)可以怎樣由子節點推出。這本質上就是考慮\(u\)

處放不放基站,以及放輻射範圍為多少的基站。我們發現,要覆蓋\(u\)有兩種型別,一種是\(u\)本身放基站,另一種是以子節點為根節點的樹的覆蓋範圍延伸到\(u\)節點。這個時候我們會發現\(f(u)\)這個狀態定義太“粗”了,需要細化。

為了細化狀態,我們可以考慮這樣定義,令\(f(u,d)\)為以\(u\)為根的樹被完全覆蓋且對於\(u\)還至少能夠繼續延伸距離\(d\)的範圍的最小花費,換句話說\(f(u,d)\)是以\(u\)為根的樹被完全覆蓋且距離\(u\)小於等於\(d\)的點被全部覆蓋的最小花費,這樣可以看出\(f(u,d)\)\(f(v,d+1)\)相關。然而我們發現就算是這樣還是不夠,為什麼呢?因為對於以\(u\)

的子節點為根的樹而言是可以通過父節點\(u\)相互延伸的,而且\(u\)節點也能夠延伸到以其子節點為根的樹。此時部分子樹就不需要完全覆蓋,只需要覆蓋某個地方以下的部分即可。

為了解決這個問題,我們需要定義另一個狀態,令\(g(u,d)\)為在以\(u\)為根節點的樹的內部,距離\(u\)大於等於\(d\)的節點被全部覆蓋的最小花費。這樣就可以寫出具體的狀態轉移了。

\[g(u,d)=\min\{g(u,d-1),\sum\limits_{i=1}^mg(v_i,d-1)\},\qquad d\geq1\\ f(u,d)=\min\left\{f(u,d+1),d+\sum\limits_{i=1}^mg(v_i,d),\min\limits_{1\leq i\leq m}\left\{f(v_i,d+1)+\sum\limits_{1\leq j\leq m\and i\neq j}g(v_j,d)\right\}\right\},\qquad d\geq1\\ f(u,0)=\min\left\{f(u,1),\min\limits_{1\leq i\leq m}\left\{f(v_i,1)+\sum\limits_{1\leq j\leq m\and i\neq j}g(v_j,0)\right\}\right\}\\ g(u,0)=f(u,0) \]

尤其注意第\(3\)

個和第\(4\)個式子(邊界的情況)

\(3\)個式子不存在父親上放基站延伸到以子節點為根的樹的情況。

\(4\)個式子不能遺漏。

注意到\(\sum\limits_{i=1}^mg(v_i,d)=g(u,d+1)\),以及\(\sum\limits_{1\leq j\leq m\and i\neq j}g(v_j,d)=g(u,d+1)-g(v_i,d)\)

且對於葉節點,\(f(u,d)=d(d\geq1)\)\(f(u,0)=1\)\(g(u,d)=0(d\geq1)\)\(g(u,0)=1\)

所以轉移部分,轉化成程式碼可以寫成

for (auto v : G[u]) {
    if (v == fa) continue;
    for (int d = 1; d < n; d++) {
        // 先不用急著取最小值(事實上也不能,因為g[u][0]還不知道)
        // 只計算和,運算結果對計算f[u][d]有幫助
        g[u][d] += g[v][d - 1];
    }
}
f[u][n - 1] = n - 1;
for (int d = n - 2; d >= 0; d--) {
    if (d > 0) f[u][d] = min(f[u][d + 1], d + g[u][d + 1]);
    else f[u][0] = f[u][1];
    for (auto v : G[u]) {
        if (v == fa) continue;
        f[u][d] = min(f[u][d], f[v][d + 1] + g[u][d + 1] - g[v][d]);
    }
}
g[u][0] = f[u][0];
for (int d = 1; d < n; d++) g[u][d] = min(g[u][d - 1], g[u][d]);

程式碼:

#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 5000 + 10;
vector<int> G[maxn];
int f[maxn][maxn], g[maxn][maxn];
int n;
void dfs(int u, int fa) {
    for (auto v : G[u]) {
        if (v == fa)
            continue;
        dfs(v, u);
        for (int d = 1; d < n; d++)
            g[u][d] += g[v][d - 1];
    }
    f[u][n - 1] = n - 1;
    for (int d = n - 2; d >= 0; d--) {
        if (d > 0)
            f[u][d] = min(f[u][d + 1], d + g[u][d + 1]);
        else
            f[u][0] = f[u][1];
        for (auto v : G[u]) {
            if (v == fa)
                continue;
            f[u][d] = min(f[u][d], f[v][d + 1] + g[u][d + 1] - g[v][d]);
        }
    }
    g[u][0] = f[u][0];
    for (int d = 1; d < n; d++)
        g[u][d] = min(g[u][d - 1], g[u][d]);
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, 0);
    printf("%d\n", g[1][0]);
    return 0;
}