换根DP

距离和

image-20230812150807200

题解

  • 我们考虑先计算以\(1\)为根时\(1\)到其他所有点的距离和

  • 我们定义\(sz_u\)为以\(u\)为根的子树中点的数量, \(dp1_u\)表示以\(u\)为根的子树中的点到\(u\)的距离和

  • 我们可以通过第一次\(dfs\)求出\(sz_u,dp1_u\)

  • \[sz_u += sz_v + 1\ (u本身) \\ dp1_u += dp1_v + sz_v \]

  • 我们定义\(dp2_u\)\(u\)的父亲作为其子树时的贡献,\(dp2_1 = 0\)\(ans_u\)表示以\(u\)为根时,其他所有点到\(u\)点的距离和

  • 容易得到\(ans_u = dp1_u + dp2_u\)

  • 我们考虑如何计算\(dp2_u\)

  • 我们可以通过第一次\(dfs\)求出\(dp2_u\)

  • \[dp2_v = ans_u - (dp1_v + sz_v) + (n - sz_v),v是u的儿子 \]

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

const int N = 2e5 + 10, M = 4e5 + 10;

int n;
vector<int> g[N];
int sz[N], dp1[N], dp2[N], ans[N];

void dfs1(int u, int par)
{
    sz[u] = 1;
    for (auto v : g[u])
    {
        if (v == par)
            continue;
        dfs1(v, u);
        dp1[u] += dp1[v] + sz[v];
        sz[u] += sz[v];
    }
}

void dfs2(int u, int par)
{
    ans[u] = dp1[u] + dp2[u];
    for (auto v : g[u])
    {
        if (v == par)
            continue;
        dp2[v] = ans[u] - (dp1[v] + sz[v]) + (n - sz[v]);
        dfs2(v, u);
    }
}

void solve()
{
    cin >> n;
    for (int i = 1; i < n; ++i)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1, 0);
    dfs2(1, 0);
    for (int i = 1; i <= n; ++i)
        cout << ans[i] << endl;
}

Accumulation Degree

image-20230812164219072

题解

  • 我们考虑先计算\(1\)作为根的时候整个水系的流量

  • 我们定义\(dp1_u\)代表以\(u\)为根的子树中的流量和

  • 所以我们可以通过第一次\(dfs\)求出\(dp1_u\)

  • \[\begin{equation} \left\{ \begin{array}{lr} dp1_u = 0,u是叶子节点\\ dp1_u = \sum min(dp1_v, w_{u,v}), v是u的儿子 \end{array} \right. \end{equation} \]

  • 我们定义\(dp2_u\)代表\(u\)的父亲作为\(u\)的子树时对流量产生的贡献,\(ans_u\)为以\(u\)为源点时整个水系的流量

  • 容易得到\(ans_u = dp1_u + dp2_u\)

  • 我们考虑如何计算\(dp2_u\)

  • 我们可以通过第二次\(dfs\)求出\(dp2_u\)\(dp2_1 = 0\)

  • \[dp2_v = min(ans_u - min(dp1_v, w_{u, v}), w_{u, v}) \]

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

const int N = 2e5 + 10, M = 4e5 + 10;

int n;
vector<pair<int, int>> g[N];
int dp1[N], dp2[N], ans[N], sz[N];

void dfs1(int u, int par)
{
    sz[u] = 1;
    for (auto [v, w] : g[u])
    {
        if (v == par)
            continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[v] == 1)
            dp1[u] += w;
        else
            dp1[u] += min(dp1[v], w);
    }
}

void dfs2(int u, int par)
{
    ans[u] = dp1[u] + dp2[u];
    for (auto [v, w] : g[u])
    {
        if (v == par)
            continue;
        dp2[v] = min(ans[u] - min(dp1[v], w), w);
        dfs2(v, u);
    }
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
    {
        g[i].clear();
        ans[i] = 0;
        dp1[i] = dp2[i] = 0;
    }
    for (int i = 1; i < n; ++i)
    {
        int u, v, w;
        cin >> u >> v >> w;
        g[u].push_back({v, w});
        g[v].push_back({u, w});
    }
    dfs1(1, 0);
    dfs2(1, 0);
    int mx = 0;
    for (int i = 1; i <= n; ++i)
        mx = max(mx, ans[i]);
    cout << mx << endl;
}

树的中心

树的中心:在树中找到一个点,他到其他所有点中的最远距离最近

显然一个点\(u\)到它的所有点中的最大距离由两部分组成:以\(u\)为根节点到子树中的最大距离(向下最大距离)和与\(u\)为起点到子树外的最大距离(向上最大距离)

\(dp[u][0]\):从\(u\)到子树中的最大距离

\(dp[u][1]\):从\(u\)到子树中的次大距离

\(dp[u][2]\):从\(u\)到子树外的最大距离

为什么我们需要一个次大距离?

因为以\(v\)为起点到其子树外的最大距离,首先它的父节点是\(u\),所以既有可能往上走(\(dp[u][2]+w\)),如果往下走距离更大,他会在\(u\)往下走,但是这个时候,如果说\(dp[u][0]=dp[v][0]+w\),即从\(u\)往下走的最大距离会经过\(v\),这就矛盾了,所以我们需要一个从\(u\)往下走的次大值

基本实现思路:

先通过\(dfs1\)自下而上求出\(dp[u][0]\)\(dp[u][1]\)

而后通过\(dfs2\)自上而下求出\(dp[u][2]\)

最后树的中心就是\(\sum{min(max(dp[u][0],dp[u][2])})\)

int n;
int dp[N][3]; // dp[u][0]:子树中的最大值,dp[u][1]:子树中的次大值,dp[u][2]:子树外的最大距离
vector<pii> g[N];

void dfs1(int u, int par)
{
    for (auto &[v, w] : g[u])
    {
        if (v == par)
            continue;
        dfs1(v, u);
        if (dp[u][0] <= dp[v][0] + w)
        {
            dp[u][1] = dp[u][0];
            dp[u][0] = dp[v][0] + w;
        }
        else if (dp[u][1] <= dp[v][0] + w)
            dp[u][1] = dp[v][0] + w;
    }
}

void dfs2(int u, int par)
{
    for (auto &[v, w] : g[u])
    {
        if (v == par)
            continue;
        if (dp[u][0] == dp[v][0] + w)
            dp[v][2] = max(dp[u][2] + w, dp[u][1] + w);
        else
            dp[v][2] = max(dp[u][2] + w, dp[u][0] + w);
        dfs2(v, u);
    }
}

Great Cow Gathering G

image-20230812220531716

题解

  • 考虑换根\(DP\)求出每个点作为集会地点的路程和

  • 我们定义\(dp1_u\)为以\(u\)为根的子树中的节点到\(u\)的路程和,\(sum_u\)为以\(u\)为根的子树中的点权和

  • 我们可以通过第一次\(dfs\)得到:

  • \[sum_u = \sum sum_v + a_u\ (u本身的点权)\\ dp1_u = \sum (dp1_v + sum_v * d_{u, v}) \]

  • 我们定义\(dp2_u\)代表\(u\)的父亲作为\(u\)的子树时产生的贡献,\(ans_u\)代表\(u\)作为集会地点时的路程和

  • 显然\(ans_u = dp1_u + dp2_u\)

  • 我们通过第二次\(dfs\)得到:

  • \[dp2_v = ans_u - (dp1_v + sum_v * d_{u,v}) + (S - sum_v) * d_{u, v}\\ S = \sum a_i \]

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

const int N = 1e5 + 10, M = 4e5 + 10;

int n, S;
int sum[N], a[N], dp1[N], dp2[N], ans[N];
vector<pair<int, int>> g[N];

void dfs1(int u, int par)
{
    sum[u] = a[u];
    for (auto [v, w] : g[u])
    {
        if (v == par)
            continue;
        dfs1(v, u);
        dp1[u] += dp1[v] + sum[v] * w;
        sum[u] += sum[v];
    }
}

void dfs2(int u, int par)
{
    ans[u] = dp1[u] + dp2[u];
    for (auto [v, w] : g[u])
    {
        if (v == par)
            continue;
        dp2[v] = ans[u] - (dp1[v] + sum[v] * w) + (S - sum[v]) * w;
        dfs2(v, u);
    }
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i];
        S += a[i];
    }
    for (int i = 1; i < n; ++i)
    {
        int u, v, w;
        cin >> u >> v >> w;
        g[u].push_back({v, w});
        g[v].push_back({u, w});
    }
    dfs1(1, 0);
    dfs2(1, 0);
    int mi = INF;
    for (int i = 1; i <= n; ++i)
        mi = min(mi, ans[i]);
    cout << mi << endl;
}

Maximum White Subtree

image-20230813011630510

题解

  • 显然对于一个节点\(u\)来说,要使得包含\(u\)的连通子图中\(cnt_1 - cnt_2\)最大化,那么\(u\)显然可以是根节点

  • 所以考虑如果每个节点作为根节点时对答案的贡献,考虑换根\(DP\)

  • 我们定义\(dp1_u\)为以\(u\)为根的子树中白点和黑点个数差的最大值

  • 我们可以通过第一次\(dfs\)求出\(dp1_u\)

  • \[dp1_u = \sum dp1_v,\ dp1_v > 0,v是u的儿子 \]

  • 我们定义\(dp2_u\)\(u\)的父亲作为\(u\)子树时对答案产生的贡献,\(ana_u\)\(u\)作为整棵树的根节点时,白点和黑点个数差的最大值

  • 显然\(ans_u = dp1_u + dp2_u\)\(dp2_1 = 0\)

  • 我们可以通过第二次\(dfs\)求出\(dp2_u\)

  • \[\begin{equation} \left\{ \begin{array}{lr} dp2_v = max(0, ans_u),\ \ dp1_v \leq0\\ dp2_v = max(0, ans_u - dp1_v),\ \ dp1_v > 0 \end{array} \right. \end{equation} \]

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

// https : // codeforces.com/problemset/problem/1324/F
const int N = 2e5 + 10, M = 4e5 + 10;

int n;
int a[N], dp1[N], dp2[N], ans[N];
vector<int> g[N];

void dfs1(int u, int par)
{
    if (a[u] == 1)
        dp1[u] = 1;
    else
        dp1[u] = -1;
    for (auto v : g[u])
    {
        if (v == par)
            continue;
        dfs1(v, u);
        if (a[v] == 1)
            dp1[u] += dp1[v];
        else if (dp1[v] > 0)
            dp1[u] += dp1[v];
    }
}

void dfs2(int u, int par)
{
    ans[u] = dp1[u] + dp2[u];
    for (auto v : g[u])
    {
        if (v == par)
            continue;
        if (a[v] == 1 || dp1[v] > 0)
        {
            if (ans[u] - dp1[v] > 0)
                dp2[v] = ans[u] - dp1[v];
            else
                dp2[v] = 0;
        }
        else
        {
            if (ans[u] > 0)
                dp2[v] = ans[u];
            else
                dp2[v] = 0;
        }
        dfs2(v, u);
    }
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    for (int i = 1; i < n; ++i)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1, 0);
    dfs2(1, 0);
    for (int i = 1; i <= n; ++i)
        cout << ans[i] << "\n "[i < n];
}
posted @ 2023-08-12 16:59  Zeoy_kkk  阅读(29)  评论(0编辑  收藏  举报