HDU 5758 Explorer Bo(树形DP)

58 篇文章 1 订阅

Description
给一棵n个点的树,每次任选两个点,然后覆盖两点间的所有边,要求以最少的次数覆盖所有的边,在保证次数最少的情况下要求覆盖边的总次数最小
Input
第一行为一整数T表示用例组数,对于每组用例首先输入一个整数n表示点数,之后n-1行每行两个整数u和v表示在树上u和v之间有一条边(T<=10,n<=100000)
Output
对于每组用例,输出在保证覆盖次数最少的情况下覆盖的边的最小总次数
Sample Input
2
3
1 2
2 3
7
1 2
1 3
2 4
2 5
3 6
3 7
Sample Output
2
8
Solution
最小覆盖次数显然是(cnt+1)/2,其中cnt为这棵树的叶子节点数量,具体覆盖策略就是每次选取两个叶子节点去覆盖,如果叶子节点是奇数则剩下的那个节点也要被覆盖一次,下面先考虑偶数个叶子节点的情况:
考虑以节点i为根的子树,如果这棵子树有奇数个节点,那么可以先在树内两两匹配这些叶子节点,之后剩下的那个节点需要出来和其他子树匹配,所以节点i与其父亲之间的那条边被用一次,如果这棵子树有偶数个叶子节点,由于i与其父亲之间的那条边至少需要被用一次,所以有一个节点要出去,这样以来就还剩一个叶子节点没有匹配,它也需要出去,故i与其父亲之间那条边需要被用两次,总之,以size[i]表示以i为根节点的子树中叶子节点数量,如果size[i]是奇数则i->fa[i]这条边要被用一次,如果size[i]是偶数则i->fa[i]这条边要被用两次,按故进行一遍树形DP求出size[i]即得到最优解
但是,如果叶子节点是奇数个的话,那么按以上策略则一定有一条链被多用了,枚举这条链显然不行,但有一种较好的处理方法就是以一个叶子节点为根节点对整棵树进行树形DP,搜索到i节点表示根节点到i节点这条链被多用了,那么这条链上每条边对答案的贡献就要改变,换个角度看,就是i点到其所在子树的某叶子节点的这条链是独立的,不需要和其他叶子节点进行匹配,那么这个叶子节点对i及其各祖先的贡献就被消除了,那么i到根节点这条链上的所有点,其size值的奇偶性就会发生改变,对答案贡献就从1变成2,从2变成1了,这样对整棵树进行一遍树形DP,取一个最大减少量,用对偶数节点的策略求出的答案减去这个最大减少量即为答案
然后解释一下为什么这样做要以一个叶子节点作为根节点root,这样做相当于先把root看作不需要匹配的那个点,这样按偶数节点那种策略求出一个解就是root孤立出去后的答案,但是可能某个其他的叶子节点不匹配会得到一个更优的解,即本来是u到i再到v到达与v匹配的目的,但是现在u和root匹配,v不匹配更优,这样一来对答案的影响就是把i到root这条边上所有点的size值减一,对答案的贡献就是1和2的反转,即为上面所述那个树形DP得到的值
Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define maxn 100005
int T,n,size[maxn],degree[maxn],dp[maxn],Max;
vector<int>g[maxn];
void dfs1(int u,int fa)
{
    size[u]=dp[u]=0;
    int flag=0;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa)continue;
        flag=1;
        dfs1(v,u);  
        if(size[v]&1)dp[u]++;
        else dp[u]+=2;
        dp[u]+=dp[v];
        size[u]+=size[v];
    }   
    if(!flag)size[u]=1;
}
void dfs2(int u,int fa,int res)
{
    Max=max(Max,res);
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa)continue;
        dfs2(v,u,res+(size[v]&1?-1:1));
    }
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)g[i].clear(),degree[i]=0;
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            degree[u]++,degree[v]++;
            g[u].push_back(v),g[v].push_back(u);
        }
        int root;
        for(int i=1;i<=n;i++)
            if(degree[i]==1)
            {
                root=i;
                break;
            }
        Max=0;
        dfs1(root,root);
        dfs2(root,root,0);
        size[root]++;
        if(size[root]&1)printf("%d\n",dp[root]-Max);
        else printf("%d\n",dp[root]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值