[SMOJ1783]移动信号

97 篇文章 0 订阅
11 篇文章 0 订阅

题目描述

给出一个树,有 N 个结点,结点编号从 1 至 N。假如在第 i 个结点建立一个信号塔,那么与第 i 个结点有边相连的结点就能接受到信号,当然第 i 个结点本身也能接受到信号。

问题是:至少要在多少个结点建立信号塔,才能使得所有的结点都能接收到信息。

输入格式 1783.in

第一行,一个整数 N 1N10,000

接下来有 N1 行,每行两个整数: a b,表示结点 a 和结点 b 有边相连。 1AN ; 1BN ; AB

输出格式 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][Build]=min{f[son][Build],f[son][DependOnFather],f[son][DependOnSon]}

如果自己不建,依赖父亲给自己提供信号,那么至少儿子不能依赖自己了,它们要么自力更生建一个,要么再依赖它们的儿子(也就是自己的孙子们),即

f[root][DependOnFather]=min{f[son][Build],f[son][DependOnSon]}

最后一种情况,自己不建,依赖儿子给自己提供信号,首先还是一样,儿子没办法依赖自己了。

显然,其实只要有一个儿子是自己建,那么它就能给自己提供信号;至于其他的儿子就可建可不建了,即

f[root][DependOnSon]=min{f[son][Build],f[son][DependOnSon]}

奇怪。看起来,依赖父亲和依赖儿子的转移方程怎么是一模一样的?这不大可能吧?

确实如此,我们忽略了一种情况。

万一,在依赖儿子的时候,所有儿子都是自己不建会更优一些,那么没有一个儿子建了信号塔。此时我们就无法依赖儿子了!

所以要单独讨论一下,对于这种特殊情况,要找一个儿子来建塔。找谁好呢?

现在我们已经算得了一个 f[root][DependOnSon] 了,当然要注意它不是正确的。

假设找儿子 i 来建塔,那么新的值就是

f[root][DependOnSon]f[i][DependOnSon]+f[i][Build]

显然要让新的值最小,那么必须让

f[i][Build]f[i][DependOnSon]
最小。

于是可以加个 bool 变量,看在考虑每一个儿子 i 的时候,f[root][DependOnSon] 是否取过某个 f[i][Build] ,顺便找出 f[i][Build]f[i][DependOnSon] 的最小值。如果始终没有取,那么就要加上这个最小值。

可能会有疑问,万一 f[i][Build]f[i][DependOnSon] 是负数呢?

不会的。如果 f[i][Build]f[i][DependOnSon] 的最小值为负数,意味着必然存在某个儿子 i ,使得 f[i][Build] 严格小于 f[i][DependOnSon] ,那么此时 f[root][DependOnSon] 就会取到 f[i][Build] 的值了,那么这就不属于不合法的情况了。

边界条件为:对于所有的叶子节点,依赖儿子的情况为无穷大,因为它们没有儿子可以依赖。

所以,我们设计状态的时候一定要注意无后效性的问题。倘若用一种可能会有后效性的状态来描述,可能就会出现漏洞,从而导致满盘皆输。

参考代码:

#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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值