翻转树边(换根dp)
题意
给定一个个节点的树,节点编号为。树中的条边均为单向边。
现在,我们需要选取一个节点作为中心点,并希望从中心点出发可以到达其他所有节点。
但是,由于树中的边均为单向边,所以在选定中心点后,可能无法从中心点出发到达其他所有节点。为此,我们需要翻转一些边的方向,从而使得所选中心点可以到达其他所有节点。
我们希望选定中心点后,所需翻转方向的边的数量尽可能少。请你确定哪些点可以选定为中心点,并输出所需的最少翻转边数量。
数据范围
思路
本题是单向边,在进行树形dp的时候图是不连通的。基于这一点,我们可以建双向边,将原来的边权值设置为,表示这个方向不用翻转;将反向边的权值设置为,表示这个方向需要翻转。
后面的过程是比较明显的换根dp。首先考虑往下走,需要翻转多少条边。我们不妨设当前点为,子节点是,往下走需要翻转的边数为。则,。
然后再考虑向上走需要翻转多少条边。我们不妨设当前点为,父节点为,往上走需要翻转的边数为。则,。
最终答案为:
代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】