翻转树边(换根dp)

题意

给定一个n个节点的树,节点编号为1n。树中的n1条边均为单向边。

现在,我们需要选取一个节点作为中心点,并希望从中心点出发可以到达其他所有节点。

但是,由于树中的边均为单向边,所以在选定中心点后,可能无法从中心点出发到达其他所有节点。为此,我们需要翻转一些边的方向,从而使得所选中心点可以到达其他所有节点。

我们希望选定中心点后,所需翻转方向的边的数量尽可能少。请你确定哪些点可以选定为中心点,并输出所需的最少翻转边数量。

数据范围

2n2×105

思路

本题是单向边,在进行树形dp的时候图是不连通的。基于这一点,我们可以建双向边,将原来的边权值设置为0,表示这个方向不用翻转;将反向边的权值设置为1,表示这个方向需要翻转。

后面的过程是比较明显的换根dp。首先考虑往下走,需要翻转多少条边。我们不妨设当前点为u,子节点是j,往下走需要翻转的边数为downu。则,downu=j(downj+wuj)

然后再考虑向上走需要翻转多少条边。我们不妨设当前点为u,父节点为f,往上走需要翻转的边数为upu。则,upu=upf+downj(wfu+downu)+wuf

最终答案为:upu+downu

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 200010, M = 2 * N;

int n;
int h[N], e[M], w[M], ne[M], idx;
int down[N], up[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void dfs1(int u, int fa)
{
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if(j == fa) continue;
        dfs1(j, u);
        down[u] += down[j] + w[i];
    }
}

void dfs2(int u, int fa)
{
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if(j == fa) continue;
        up[j] = up[u] + down[u] - (down[j] + w[i]) + w[i ^ 1];
        dfs2(j, u);
    }
}

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for(int i = 0; i < n - 1; i ++) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b, 0), add(b, a, 1);
    }
    dfs1(1, -1);
    dfs2(1, -1);
    int mi = 1e9;
    for(int i = 1; i <= n; i ++) {
        mi = min(mi, up[i] + down[i]);
    }
    printf("%d\n", mi);
    for(int i = 1; i <= n; i ++) {
        if(up[i] + down[i] == mi) {
            printf("%d ", i);
        }
    }
    printf("\n");
    return 0;
}
posted @   pbc的成长之路  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示