题意
给定一个\(n\)个节点的树,节点编号为\(1 \sim n\)。树中的\(n−1\)条边均为单向边。
现在,我们需要选取一个节点作为中心点,并希望从中心点出发可以到达其他所有节点。
但是,由于树中的边均为单向边,所以在选定中心点后,可能无法从中心点出发到达其他所有节点。为此,我们需要翻转一些边的方向,从而使得所选中心点可以到达其他所有节点。
我们希望选定中心点后,所需翻转方向的边的数量尽可能少。请你确定哪些点可以选定为中心点,并输出所需的最少翻转边数量。
数据范围
\(2 \leq n \leq 2 \times 10^5\)
思路
本题是单向边,在进行树形dp的时候图是不连通的。基于这一点,我们可以建双向边,将原来的边权值设置为\(0\),表示这个方向不用翻转;将反向边的权值设置为\(1\),表示这个方向需要翻转。
后面的过程是比较明显的换根dp。首先考虑往下走,需要翻转多少条边。我们不妨设当前点为\(u\),子节点是\(j\),往下走需要翻转的边数为\(down_u\)。则,\(down_u = \sum\limits_j (down_j + w_{uj})\)。
然后再考虑向上走需要翻转多少条边。我们不妨设当前点为\(u\),父节点为\(f\),往上走需要翻转的边数为\(up_u\)。则,\(up_u = up_f + down_j - (w_{fu} + down_u) + w_{uf}\)。
最终答案为:\(up_u + down_u\)
代码
#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;
}