HDU 4714 Tree2cycle(树形dp)

17 篇文章 0 订阅

题目链接;
HDU 4714 Tree2cycle
题意:
给一个 n 个节点和n1条边的树。要求把这棵树变成一个环(所有的点都在环上),每破坏一条边和新建一条边的代价都是1,求最小的代价。
数据范围: n106
分析:
我们来考虑保留边的最大数量,记为 left 。首先原来有 n1 条边,保留了 left 条边,那就意味着要破坏 n1left 条边。要变成环的话,环上有 n 条边,所以需要新建nleft条边,总代价为:

(n1left)+(nleft)=2n2left1

显然 left 越大越好。一开始我以为 left 就是树的直径而且傻乎乎的写完交上去了,直到返回 WrongAnswer 。。。。。。其实仔细想一下就知道明显算少了。
我们可以考虑每个点的度。保留的边必须使得每个点的度小于等于2。于是我们可以定义 dp[u][0],dp[u][1],dp[u][2] 为使得以 u 为根的子树的所有节点的度小于等于2,并且 u 节点的度分别为0,1,2时保留边的最大数量。写的时候发现这样子定义不太好转移,于是放宽定义,定义 dp[u][0],dp[u][1],dp[u][2] 为使得以 u 为根的子树的所有节点的度小于等于2,并且 u 节点的度分别小于等于0,1,2保留边的最大数量。
对于 u 的每个儿子v,都是要取 dp[v][0],dp[v][1],dp[v][2] 中的最大值。当存在 max(dp[v][0],dp[v][1])>=dp[v][2] 时,我们就可以保留 uv 之间的边,这时统计这样的边的数量来确定 dp[u][1]dp[u][2] 是否可以额外加1和额外加2。

时间复杂度: O(n)

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1000010;

int T, n, total;
int head[MAX_N], dp[MAX_N][5];

struct Edge {
    int v, next;
}edge[MAX_N * 2];

void AddEdge (int u, int v)
{
    edge[total].v = v;
    edge[total].next = head[u];
    head[u] = total++;
}

void dfs(int u, int p)
{
    int flag1 = 0, flag2 = 0;
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].v;
        if (v == p) continue;
        dfs(v, u);

        dp[u][0] += max(dp[v][0], max(dp[v][1], dp[v][2]));
        dp[u][1] += max(dp[v][0], max(dp[v][1], dp[v][2]));
        dp[u][2] += max(dp[v][0], max(dp[v][1], dp[v][2]));

        if(max(dp[v][1], dp[v][0]) >= dp[v][2]) {
            flag1++;
            flag2++;
        }
    }
    if (flag1) dp[u][1]++, dp[u][2]++;
    if (flag2 >= 2) dp[u][2]++;
}

int main()
{
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        memset(head, -1, sizeof(head));
        total = 0;
        for (int i = 1; i < n; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            AddEdge(u, v);
            AddEdge(v, u);
        }
        memset(dp, 0, sizeof(dp));
        dfs (1, -1);
        int left = max(dp[1][0], max(dp[1][1], dp[1][2]));
        printf("%d\n", n * 2 - 2 * left - 1);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值