[BZOJ1907]-树的路径覆盖-树形DP||贪心

说在前面

并没有什么想说的,但是要保持格式


题目

BZOJ1907传送门

题目大意

给出一个N个节点的树,求出树的最小路径覆盖
路径不能有公共点,即不能相交

输入输出格式

输入格式:
第一行一个整数T,表示数据组数
对于每组:第一行一个整数N表示点数。接下来N-1行,每行两个数字u,v描述边

输出格式:
每组数据输出一行一个整数,表示答案


解法

先说DP做法

如果所有点都独立算作一条路径,那么路径条数为N。在这个基础之上,每选一条边去连接两个度数不为2的节点,相当于合并了两条链,因此条数-1。现在就是需要求最多能选中多少条边
很显然的,在任何一种路径覆盖方案中,每个点的度数只会是0,1,2。于是定义dp[u][0/1/2]表示只考虑子树的情况下,u号节点度数为0/1/2时,最多能选多少条边。然后可以发现dp[u][0]其实就是max(dp[son][0/1/2])之和,而叶结点的dp[u][0]是0,因此度数为0这种情况对最终答案没有贡献。然后还可以发现,只有dp[son][0/1]可以转移到dp[u],而转移完成之后,dp[son][0/1]就再也用不上了,因此实际上可以把dp[u][0]与dp[u][1]合并。

那么定义出状态:dp[u][1/2]表示,u号节点所在链没有终止(度数小于2)/已经终止(度数为2)时,子树选择边数的最大值。转移是显然的,分当前点不与子节点连接(度数为0,也就是初值),与一个子节点连接(度数为1),与两个子节点连接(度数为2)三种情况。细节可以自行思考,很简单,代码也很短

于是贪心做法…

可以发现,当前点如果可以和子节点连接,那么一定是连接最优,然后直接贪即可


下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

short T , N , tp , head[10005] , dp[10005][3] , fa[10005] ;
struct Path{
    short pre , to ;
}p[20005] ;

short size_of_short = sizeof( short ) ;
void clear(){
    tp = 0 ;
    memset( head + 1 , 0 , N * size_of_short ) ;
}

void In( short t1 , short t2 ){
    p[++tp].pre = head[t1] ;
    p[ head[t1] = tp ].to = t2 ;
}

void dfs( short u ){
    short sum = 0 , tmp = 0 ;
    for( short i = head[u] ; i ; i = p[i].pre ){
        short v = p[i].to ;
        if( v == fa[u] ) continue ;
        fa[v] = u , dfs( v ) ;
        if( dp[v][1] < dp[v][2] ) sum += dp[v][2] ;
        else{
            tmp ++ ;
            sum += dp[v][1] ;
        }
    }
    dp[u][1] = dp[u][2] = sum ;
    if( tmp >= 1 ) dp[u][1] ++ ;
    if( tmp >= 2 ) dp[u][2] += 2 ;
}

inline void read_( short &x ){
    x = 0 ;
    char ch = getchar() ;
    while( ch < '0' || ch > '9' ) ch = getchar() ;
    while( ch >='0' && ch <='9' ) x = (x<<1)+(x<<3)+ch-'0' , ch = getchar() ;
}

int main(){
    scanf( "%hd" , &T ) ;
    while( T -- ){
        scanf( "%hd" , &N ) ;
        clear() ;
        for( short i = 1 , u , v ; i < N ; i ++ ){
            read_( u ) , read_( v ) ;
            In( u , v ) ; In( v , u ) ;
        }
        dfs( 1 ) ;
        printf( "%hd\n" , N - max( dp[1][2] , dp[1][1] ) ) ;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值