Educational Codeforces Round 67 (Rated for Div. 2) E. Tree Painting(换根DP)

文章探讨如何在树中选择初始黑色节点以最大化后续染色过程中的权值总和,借助DFS和DP方法求解。
摘要由CSDN通过智能技术生成

原题链接:E. Tree Painting


题目大意:


给定一棵 n n n 个点, n − 1 n-1 n1 条边的树,最初时所有节点都是白色的,你要进行 n n n 次染色使得整棵树都变成黑色。

操作规则如下:

  • 开始时,你可以任选一个点染成黑色。
  • 之后,你可以任选一个与黑色的节点相连白色节点,将其染成黑色,并获得该节点被染色前所在白色连通块大小的权值。

问你进行操作后能获得的权值之和最大为多少。

解题思路


通过手摸样例我们发现,答案在我们第一步确定第一个黑色节点时就已经固定了,和操作顺序无关,那我们只需要计算选哪一个点作为第一个黑色节点最大即可。

考虑怎么计算选点后的权值之和,假设 s o n u son_u sonu 为节点 u u u 的儿子节点集合, s i z u siz_u sizu 为节点 u u u 的子树大小, d p u dp_u dpu 为子树对答案的贡献之和。

那么递推式就是:

d p [ u ] = s i z [ u ] + ∑ v ∈ s o n u d p [ v ] \begin{array}{c} dp[u]= siz[u]+\sum_{v \in son_u}dp[v] \end{array} dp[u]=siz[u]+vsonudp[v]

这样,我们用一个 D F S DFS DFS 就可以在 O ( n ) O(n) O(n) 的时间内把选 u u u 点作为根节点的总答案 d p [ u ] dp[u] dp[u] 计算出来了。

但是这样我们只计算了 u u u 作为根节点的答案,对于其他点作为根节点的答案我们还不知道,而枚举所有点的复杂度是 O ( n 2 ) O(n^2) O(n2) 的,不可接受。

如果我们能够用 d p [ u ] dp[u] dp[u] 的答案计算 d p [ v ] dp[v] dp[v] 的答案就好了。

引入知识点:换根 D P DP DP

假设我们已经计算好了 d p [ 1 ] dp[1] dp[1] 的值,接下来要计算 d p [ 4 ] dp[4] dp[4] 的值,想想我们要怎么让答案从 d p [ 1 ] dp[1] dp[1] 转移到 d p [ 4 ] dp[4] dp[4]。(下图)

在这里插入图片描述

我们假设 g u g_u gu 为每个点 u u u 作为根的最终答案,那么显然

g [ 1 ] = d p [ 1 ] \begin{array}{c} g[1]=dp[1] \end{array} g[1]=dp[1]

假设我们要计算 g [ 4 ] g[4] g[4] ,我们假设 4 4 4 为此时的根,推推式子:

d p ′ [ 1 ] dp'[1] dp[1] 是以 4 4 4 作为根, 1 1 1 的子树对答案的贡献, s i z ′ [ 1 ] siz'[1] siz[1] 为以 4 4 4 为根时 1 1 1 的子树大小, s i z ′ [ 4 ] siz'[4] siz[4] 为以 4 4 4 为根时整棵树的大小,那么我们考虑将 g [ 4 ] g[4] g[4] 从以 g [ 1 ] g[1] g[1] 转化过来。

g [ 4 ] = s i z ′ [ 4 ] + ∑ v ∈ s o n 4 d p [ v ] + d p ′ [ 1 ] \begin{array}{c} g[4]= siz'[4]+\sum_{v \in son_4}dp[v] + dp'[1] \end{array} g[4]=siz[4]+vson4dp[v]+dp[1]
考虑怎么快速算出 d p ′ [ 1 ] dp'[1] dp[1] 的值。
d p ′ [ 1 ] = s i z ′ [ 1 ] + ∑ v ∈ s o n 1 ∣ v ∉ 4 d p [ v ] = s i z ′ [ 1 ] + ∑ v ∈ s o n 1 d p [ v ] − d p [ 4 ] \begin{array}{c} dp'[1]=siz'[1]+\sum_{v \in son_1 | v \notin4}dp[v]\\ =siz'[1]+\sum_{v \in son_1} dp[v]-dp[4] \end{array} dp[1]=siz[1]+vson1v/4dp[v]=siz[1]+vson1dp[v]dp[4]

注意到: s i z ′ [ 1 ] = n − s i z [ 4 ] = s i z [ 1 ] − s i z [ 4 ] siz'[1]=n-siz[4]=siz[1]-siz[4] siz[1]=nsiz[4]=siz[1]siz[4]
d p ′ [ 1 ] = s i z ′ [ 1 ] + ∑ v ∈ s o n 1 d p [ v ] − d p [ 4 ] = s i z [ 1 ] − s i z [ 4 ] + ∑ v ∈ s o n 1 d p [ v ] − d p [ 4 ] = s i z [ 1 ] + ∑ v ∈ s o n 1 d p [ v ] − d p [ 4 ] − s i z [ 4 ] = g [ 1 ] − d p [ 4 ] − s i z [ 4 ] \begin{array}{c} dp'[1]=siz'[1]+\sum_{v \in son_1} dp[v]-dp[4]\\ =siz[1]-siz[4]+\sum_{v \in son_1} dp[v]-dp[4]\\ =siz[1]+\sum_{v \in son_1} dp[v]-dp[4]-siz[4]\\ =g[1]-dp[4]-siz[4] \end{array} dp[1]=siz[1]+vson1dp[v]dp[4]=siz[1]siz[4]+vson1dp[v]dp[4]=siz[1]+vson1dp[v]dp[4]siz[4]=g[1]dp[4]siz[4]

带回原式:
g [ 4 ] = s i z ′ [ 4 ] + ∑ v ∈ s o n 4 d p [ v ] + d p ′ [ 1 ] = s i z ′ [ 4 ] + ∑ v ∈ s o n 4 d p [ v ] + g [ 1 ] − d p [ 4 ] − s i z [ 4 ] \begin{array}{c} g[4] = siz'[4] + \sum_{v \in son_4}dp[v] + dp'[1]\\ = siz'[4] + \sum_{v \in son_4}dp[v]+g[1]-dp[4]-siz[4]\\ \end{array} g[4]=siz[4]+vson4dp[v]+dp[1]=siz[4]+vson4dp[v]+g[1]dp[4]siz[4]

注意到: s i z ′ [ 4 ] = n , d p [ 4 ] = s i z [ 4 ] + ∑ v ∈ s o n 4 d p [ v ] siz'[4]=n,dp[4]=siz[4]+\sum_{v \in son_4}dp[v] siz[4]=n,dp[4]=siz[4]+vson4dp[v]

g [ 4 ] = s i z ′ [ 4 ] + ∑ v ∈ s o n 4 d p [ v ] + g [ 1 ] − d p [ 4 ] − s i z [ 4 ] = n + g [ 1 ] − s i z [ 4 ] − s i z [ 4 ] = g [ 1 ] + n − 2 × s i z [ 4 ] \begin{array}{c} g[4] = siz'[4] + \sum_{v \in son_4}dp[v]+g[1]-dp[4]-siz[4]\\ =n+g[1]-siz[4]-siz[4] \\ =g[1]+n-2 \times siz[4] \end{array} g[4]=siz[4]+vson4dp[v]+g[1]dp[4]siz[4]=n+g[1]siz[4]siz[4]=g[1]+n2×siz[4]

那么通式就是:

g [ v ] = g [ u ] + n − 2 × s i z [ v ] \begin{array}{c} g[v]=g[u]+n-2 \times siz[v] \end{array} g[v]=g[u]+n2×siz[v]

( u u u 为与 v v v 相邻的,已经算出 g u g_u gu 的节点)

那么这样我们就算出了以 4 4 4 为根获得的答案 g [ 4 ] g[4] g[4],这样,我们还能继续算与 4 4 4 相邻的节点 g [ 9 ] g[9] g[9],算完 g [ 9 ] g[9] g[9] 还能再算与 9 9 9 相邻的节点 g [ 7 ] , g [ 8 ] g[7],g[8] g[7],g[8]

这样,我们只需要先算出任意一个点为根的答案 g u g_u gu ,再用一个 D F S DFS DFS 就能将原图的所有以 v v v 为根的答案算出来了。

时间复杂度: O ( n ) O(n) O(n)

AC代码


#include <bits/stdc++.h>
#define NO return void(cout << "No\n")
#define YES return void(cout << "Yes\n")
using namespace std;

using i64 = long long;
using PII = pair<int, int>;

const int N = 2e5 + 10;

vector<int> G[N];
i64 g[N], dp[N], siz[N];
int n;

void DFS1(int u, int ufa) {
    siz[u] = 1;
    for (auto& v : G[u]) {
        if (siz[v]) continue;
        DFS1(v, u);
        siz[u] += siz[v];
        dp[u] += dp[v];
    }
    dp[u] += siz[u];
}

void DFS2(int u, int ufa) {
    if (u != 1) {
        //通式:
        g[u] = g[ufa] + n - 2 * siz[u];
    }
    for (auto& v : G[u]) {
        if (v == ufa) continue;
        DFS2(v, u);
    }
}

void solve() {

    cin >> n;
    for (int i = 1; i <= n - 1; ++i) {
        int u, v; cin >> u >> v;
        G[u].emplace_back(v);
        G[v].emplace_back(u);
    }

    //随便取一个点先算出一个 g1 的值
    DFS1(1, 0);

    g[1] = dp[1];

    //第二次利用 g1 的值 算出其他 gu 的值
    DFS2(1, 0);

    i64 ans = 0;
    for (int i = 1; i <= n; ++i) {
        ans = max(ans, g[i]);
    }

    cout << ans << '\n';
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1; //cin >> t;
    while (t--) solve();

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值