题目描述
给出一个树,有 N 个结点,结点编号从 1 至
N 。假如在第 i 个结点建立一个信号塔,那么与第i 个结点有边相连的结点就能接受到信号,当然第 i 个结点本身也能接受到信号。问题是:至少要在多少个结点建立信号塔,才能使得所有的结点都能接收到信息。
输入格式 1783.in
第一行,一个整数
N 。 1≤N≤10,000接下来有 N−1 行,每行两个整数: a
b ,表示结点 a 和结点b 有边相连。 1≤A≤N ; 1≤B≤N ; A≠B
输出格式 1783.out
一个整数。
输入样例 1783.in
5
1 3
5 2
4 3
3 5
输出样例 1783.out
2
这个题……也是一道好题,挺能够考察细心程度的。
题目意思写得已经够简洁明了,这里就不再重复概括一下了,反正也不会短到哪里去。
此题我初做的时候,只用 f[root] [0 不放,1 放] 表示使以 root 为根的子树中所有结点都收到信号所需建的最少信号塔数量,结果爆零了。
这是因为,我忽略了一个非常关键的问题。
不妨先把样例画出来观察一下吧。
如果在 3 建一个信号塔的话,不仅会对它的儿子 1 和 4 造成影响,同时也会影响到它的父亲 5!
也就是说,我们做 DP 的时候还不能单纯只考虑儿子,还得想想父亲。那这样不是会有后效性的吗?
一开始我也是这样想的,但是,仔细思考一下。其实一个结点对父亲的影响只有一层而已,到了它爷爷那一辈,最多跟它父亲有联系,跟它自己可就没了。
于是我们可以这样记,首先前提条件是自己和子树一定要全部收到信号,然后有三种情况:要么是自己建一个信号塔,要么是自己不建,靠父亲给自己提供信号;要么是自己不建,靠儿子(们)给自己提供信号。
有了这样的状态就非常好办了,每种情况考虑清楚就可以转移了。
对于自己建信号塔的情况,儿子建不建都无所谓,也就是
如果自己不建,依赖父亲给自己提供信号,那么至少儿子不能依赖自己了,它们要么自力更生建一个,要么再依赖它们的儿子(也就是自己的孙子们),即
最后一种情况,自己不建,依赖儿子给自己提供信号,首先还是一样,儿子没办法依赖自己了。
显然,其实只要有一个儿子是自己建,那么它就能给自己提供信号;至于其他的儿子就可建可不建了,即
奇怪。看起来,依赖父亲和依赖儿子的转移方程怎么是一模一样的?这不大可能吧?
确实如此,我们忽略了一种情况。
万一,在依赖儿子的时候,所有儿子都是自己不建会更优一些,那么没有一个儿子建了信号塔。此时我们就无法依赖儿子了!
所以要单独讨论一下,对于这种特殊情况,要找一个儿子来建塔。找谁好呢?
现在我们已经算得了一个 f[root][DependOnSon] 了,当然要注意它不是正确的。
假设找儿子 i 来建塔,那么新的值就是
显然要让新的值最小,那么必须让
于是可以加个 bool 变量,看在考虑每一个儿子
i
的时候,
可能会有疑问,万一 f[i][Build]−f[i][DependOnSon] 是负数呢?
不会的。如果
f[i][Build]−f[i][DependOnSon]
的最小值为负数,意味着必然存在某个儿子
i
,使得
边界条件为:对于所有的叶子节点,依赖儿子的情况为无穷大,因为它们没有儿子可以依赖。
所以,我们设计状态的时候一定要注意无后效性的问题。倘若用一种可能会有后效性的状态来描述,可能就会出现漏洞,从而导致满盘皆输。
参考代码:
#include <algorithm>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 1e5 + 100;
int n;
//采用数组模拟指针储存树
int head[maxn];
struct Edge {
int to;
int next;
Edge () { to = next = 0; }
} edge[maxn];
int cnt = 0;
void addEdge(int u, int v) {
edge[++cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
int dp[maxn][5]; //0 依赖于儿子 1 自己建 2 依赖于父亲
void dfs(int root, int pre) {
dp[root][1] = 1;
bool f = false; //看是否每个儿子都不建塔
int minD = INT_MAX; //
for (int i = head[root]; i; i = edge[i].next) {
int child = edge[i].to;
if (child != pre) {
dfs(child, root);
dp[root][0] += min(dp[child][0], dp[child][1]);
//若该儿子建塔更优,则根结点是可以依靠它的
f = f || dp[child][1] <= dp[child][0]; minD = min(minD, dp[child][1] - dp[child][0]);
dp[root][1] += min(dp[child][0], min(dp[child][1], dp[child][2]));
dp[root][2] += min(dp[child][0], dp[child][1]);
}
}
if (!f) dp[root][0] += minD; //所有儿子都不建塔,强制建一个代价最小的
if (!dp[root][0]) dp[root][0] = INT_MAX; //叶子结点不能靠儿子
}
int main(void) {
freopen("1783.in", "r", stdin);
freopen("1783.out", "w", stdout);
scanf("%d", &n);
memset(head, 0, sizeof head);
for (int i = 1; i < n; i++) {
int a, b;
scanf("%d%d", &a, &b);
addEdge(a, b); addEdge(b, a);
}
memset(dp, 0, sizeof dp);
dfs(1, 0);
printf("%d\n", min(dp[1][0], dp[1][1]));
return 0;
}