题解 P2899 [USACO08JAN]Cell Phone Network G
题目描述
给定一棵 \(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;
}