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;
}