题解 P2899 [USACO08JAN]Cell Phone Network G

题目描述

Link

给定一棵 \(n\) 个点的树,如果一个点是信号塔或者和这个点直接连接的点是信号塔,那这个点就能收到信号,问最少要建多少个信号塔,才能让所有点都能收到信号。

\(n \leq 10^4\)

Solution

下面定义 \(i\)\(j\) 看到表示 \(i\)\(j\) 直接相连并且 \(j\) 是信号塔。

这题比一般的树形 \(dp\) 要复杂一点,因为一个点有三种情况:自己是信号塔,父亲节点是信号塔,儿子节点是信号塔。

所以我们设 \(f_{i,0/1/2}\) 表示在以 \(i\) 为根的子树中,整个子树都能被看到且 \(i\)\(i\) 的父亲 \(/\) \(i\) 的一个子节点 \(/\) \(i\) 本身看到 所要建设最少的信号塔数量。

如果 \(i\) 被父亲看到,那么 \(i\) 不是信号塔,那么 \(i\) 的儿子们就不能看到,也就是说:

\[f_{i,0} = \sum_{j \in son(i)} \min\{f_{j,1} ,f_{j,2}\} \]

如果 \(i\) 本身是信号塔,那么 \(i\) 的儿子怎么样都行,也就是说:

\[f_{i,2} = 1+\sum_{j\in son(i)} \min\{f_{j,0},f_{j,1},f_{j,2}\} \]

如果 \(i\) 被某个子节点看到,那么就需要枚举一下 \(i\) 被哪个子节点 \(v\) 看到,对于其他的子节点,它们可以自己是灯塔,或者被它们的某个子节点看到(父节点不是信号塔,不能被父亲看到),对于子节点 \(v\) ,它必须是信号塔,也就是说:

\[f_{i,1} = \min_{v\in son(i)}\{f_{v,2}+\sum_{j\in son(i) \and j \neq v} \min\{f_{j,0},f_{j,2}\}\} \]

但是这里不能直接转移(不然复杂度是 \(\mathcal{O}(n^2)\) 的),我们需要做个容斥,设 \(sum=\sum_{j \in son(i)}\min\{f_{j,0} ,f_{j,1}\}\)

那么:

\[f_{i,1} = \min_{v\in son(i)}\{sum - \min\{f_{v,0} ,f_{v,2}\}+f_{v,2}\} \]

前面两项对应着上面那个式子的后一个求和,读者可以自己推一推,理解一下。

代码如下:

#include <cstdio>
#include <cstring>
#include <cctype>
inline int read() {
    int num = 0 ,f = 1; char c = getchar();
    while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
    while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
    return num * f;
}
inline int min(int a ,int b) {return a < b ? a : b;}
const int N = 1e4 + 5 ,M = 2e4 + 5 ,INF = 0x3f3f3f3f;
struct Edge {
    int to ,next;
    Edge (int to = 0 ,int next = 0) :
        to(to) ,next(next) {}
}G[M]; int head[N] ,cnt;
inline void add(int u ,int v) {
    G[++cnt] = Edge(v ,head[u]); head[u] = cnt;
    G[++cnt] = Edge(u ,head[v]); head[v] = cnt;
}
int f[N][3];
inline void dfs(int now ,int fa) {
    f[now][2] = 1;
    for (int i = head[now]; i ; i = G[i].next) {
        int v = G[i].to;
        if (v == fa) continue;
        dfs(v ,now);
        f[now][2] += min(min(f[v][0] ,f[v][1]) ,f[v][2]);
        f[now][0] += min(f[v][1] ,f[v][2]);
    }
    //可以发现,这里的 f[now][0] 就是上面的 sum ,直接拿来用就可以
    f[now][1] = INF; //一定要初始化,否则可能取到 0 
    for (int i = head[now]; i ; i = G[i].next) {
        int v = G[i].to;
        if (v == fa) continue;
        f[now][1] = min(f[now][1] ,f[now][0] - min(f[v][1] ,f[v][2]) + f[v][2]);
    }
}
int n;
signed main() {
    n = read();
    for (int i = 1; i <= n - 1; i++) {
        int u = read() ,v = read();
        add(u ,v);
    }
    dfs(1 ,0);
    printf("%d\n" ,min(f[1][1] ,f[1][2])); //1 不能被父亲看到,因为没有父亲
    return 0;
}
posted @ 2021-02-25 09:40  recollector  阅读(100)  评论(0编辑  收藏  举报