[CodeForces 1394D] Boboniu and Jianghu(树形 DP + 贪心) | 错题本

24 篇文章 1 订阅
11 篇文章 0 订阅

文章目录

题目

[CodeForces 1394D] Boboniu and Jianghu

分析

考虑树形 DP:对于当前点 u u u 和它的子节点 v v v,若 h u ≠ h v h_u \neq h_v hu=hv,那么这条边的方向就确定;若 h u = h v h_u = h_v hu=hv,我们需要给这条边定向。假设最终定完 u u u 所有邻接边的向后 u u u 的入度为 x x x,出度为 y y y,那么 u u u 对答案的贡献就是 t u × max ⁡ { x , y } t_u \times \max\{x, y\} tu×max{x,y}
于是可以定义状态: d p [ u ] [ 0 / 1 ] dp[u][0/1] dp[u][0/1] u u u 的父亲边朝向 u u u / / / 背向 u u u u u u 子树对答案的最小贡献。

树上给边定向的套路就是先给边钦定一个方向(例如我们钦定这些边都指向 v v v)并统计代价(代价为 d p [ v ] [ 0 ] dp[v][0] dp[v][0]),然后把改变该边方向的代价(即 d p [ v ] [ 1 ] − d p [ v ] [ 0 ] dp[v][1] - dp[v][0] dp[v][1]dp[v][0])放入数组中排序。从改变代价小的开始改变边的方向即可。

上述方法为什么是对的呢?因为对于一种改变了 p p p 条边方向的方案,其对“ u u u 对答案的贡献”的改变量是一定的(不考虑父亲边就是 t u × max ⁡ { x + p , y − p } t_u \times \max\{x + p, y - p\} tu×max{x+p,yp}),那么只需要贪心地取边改变量最小的 p p p 条即可。

代码

#include <bits/stdc++.h>

int Read() {
    int x = 0; char c = getchar();
    while (c < '0' || c > '9')
        c = getchar();
    while (c >= '0' && c <= '9')
        x = x * 10 + (c ^ 48), c = getchar();
    return x;
}

typedef long long LL;

const int MAXN = 200000;

int N;
int A[MAXN + 5], B[MAXN + 5];
std::vector<int> G[MAXN + 5];

LL Ans;
LL Dp[MAXN + 5][2];

void Dfs(int u, int f) {
    LL tot = 0;
    int out = 0, in = 0;
    std::vector<LL> num;
    for (int v: G[u]) {
        if (v != f) {
            Dfs(v, u);
            if (A[u] < A[v])
                tot += Dp[v][1], in++;
            else {
                tot += Dp[v][0], out++;
                if (A[u] == A[v])
                    num.push_aback(Dp[v][1] - Dp[v][0]);
            }
        }
    }
    std::sort(num.begin(), num.end());
    Dp[u][0] = tot + (LL)B[u] * std::max(in + (u != 1), out);
    Dp[u][1] = tot + (LL)B[u] * std::max(in, out + (u != 1));
    for (int val: num) {
        in++, out--, tot += val;
        Dp[u][0] = std::min(Dp[u][0], tot + (LL)B[u] * std::max(in + (u != 1), out));
        Dp[u][1] = std::min(Dp[u][1], tot + (LL)B[u] * std::max(in, out + (u != 1)));
    }
}

int main() {
    N = Read();
    for (int i = 1; i <= N; i++)
        B[i] = Read();
    for (int i = 1; i <= N; i++)
        A[i] = Read();
    for (int i = 1; i < N; i++) {
        int u = Read(), v = Read();
        G[u].push_back(v);
        G[v].push_back(u);
    }
    Dfs(1, 0);
    printf("%lld", Dp[1][0]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.csdn.net/qq_45458915/article/details/104831678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值