C++解题报告:电话网络——巧用树形DP

电话网络

题目描述

Farmer John决定为他的所有奶牛都配备手机,以此鼓励她们互相交流。
不过,为此FJ必须在奶牛们居住的N(1 <= N <= 10,000)块草地中选一些建上
无线电通讯塔,来保证任意两块草地间都存在手机信号。所有的N块草地按1..N
顺次编号。
    所有草地中只有N-1对是相邻的,不过对任意两块草地A和B(1 <= A <= N; 
1 <= B <= N; A != B),都可以找到一个以A开头以B结尾的草地序列,并且序列
中相邻的编号所代表的草地相邻。无线电通讯塔只能建在草地上,一座塔的服务
范围为它所在的那块草地,以及与那块草地相邻的所有草地。

    请你帮FJ计算一下,为了建立能覆盖到所有草地的通信系统,他最少要建
多少座无线电通讯塔。

输入


程序名: tower

输入格式:

* 第1行: 1个整数,N

* 第2..N行: 每行为2个用空格隔开的整数A、B,为两块相邻草地的编号
 

输出

输出格式:

* 第1行: 输出1个整数,即FJ最少建立无线电通讯塔的数目

输出样例 (tower.out):

样例输入

Copy (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

5
1 3
5 2
4 3
3 5

样例输出

2

提示

输入说明:

    Farmer John的农场中有5块草地:草地1和草地3相邻,草地5和草地2、草地

4和草地3,草地3和草地5也是如此。更形象一些,草地间的位置关系大体如下:

(或是其他类似的形状)

               4  2

               |  |

            1--3--5

输出说明:

FJ可以选择在草地2和草地3,或是草地3和草地5上建通讯塔


思路详解

首先我们可以知道这是一个无根树,就直接将无根转换为有根树,即设1为根节点

然后对于 x 节点,有三种情况

1. x 节点的子树和 x 节点被全覆盖,x 节点上有塔

2. x 节点的子树和 x 节点被全覆盖,x 节点无塔

3. x 节点的子树被全覆盖,但 x 节点未被覆盖


那么就用 dp[ x ][ 0 ] 表示第 1 种情况,dp[ x ][ 1 ] 表示第 2 种情况,dp[ x ][ 2 ] 表示第 3 种情况

用 s 表示子节点

对于第 1 种情况,其子节点的所有情况都可以满足全覆盖

dp[ x ][ 0 ] += min( dp[ s ][ 0 ] , min( dp[ s ][ 1 ] , dp[ s ][ 2 ] ) ) 


对于第 3 种情况,必须由第 2 种情况转移

dp[ x ][ 2 ] += dp[ s ][ 1 ]

因为如果其子节点上有塔,则 x 节点一定被覆盖


对于第 2 种情况,则需要累加覆盖 x 子树的最小花费,其子节点上一定会有塔

而其有很多子节点,所以需要累加子节点各种情况最小的和 ( 不是很好理解,需要画图理解 )

这里用 sum 表示求出的覆盖子树最小和

dp[ x ][ 1 ] = min( dp[ x ][ 1 ] , dp[ s ][ 0 ] + sum - min( dp[ s ][ 0 ] , dp[ s ][ 1 ] ) )


代码

结合代码理解消化吧

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
/*
dp[i][0] 表示i结点为根的子树被覆盖并且 i结点上有塔
  
dp[i][1] 表示i结点为根的子树被覆盖并且i结点上没有塔
  
dp[i][2]表示i结点为根的子树除了i全被覆盖了
*/
   
int n , dp[10007][3] ;
vector <int> G[10007] ;
  
void dfs(int x , int fa )
{
    dp[x][0] = 1 ;
    dp[x][1] = 7777777;
    int sum = 0 ;
    int z = G[x].size() ;
    for(int i = 0 ; i < z ; ++ i ) {
        int s = G[x][i];
        if( s == fa )
            continue;
        dfs( s , x );
        dp[x][0] += min( dp[s][0] , min(dp[s][1] , dp[s][2] ) );//可以由这三种情况转移 
        sum += min( dp[s][0] , dp[s][1] );//进行在 x 节点下的节点全覆盖求和 
        dp[x][2] += dp[s][1];//要让 x 节点未被覆盖,必须由其子节点的dp[s][1]转移 
    }
    if( z == 1 && x!= 1 ) return ;
    for(int i = 0 ; i < z ; ++ i )
    {
        int s = G[x][i];
        if( s == fa )
            continue;
        dp[x][1] = min( dp[x][1] , dp[s][0] + sum - min( dp[s][0] , dp[s][1] ));//该情况由在其子节点建塔dp[s][0]加上其子节点全覆盖的和 减去 之前在该状态下的累加 
    }
}
   
int main()
{
    scanf("%d", &n );
    for(int i = 1 ; i < n ; ++ i )
    {
        int a , b ;
        scanf("%d%d", &a , &b );
        G[a].push_back(b);
        G[b].push_back(a);
    }
    dfs( 1 , 0 );
    printf("%d", min(dp[1][0] , dp[1][1] )); 
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值