题解:洛谷 P11251 美丽路径
【题目来源】
洛谷:P11251 [GESP202409 八级] 美丽路径 - 洛谷
【题目描述】
小杨有一棵包含 \(n\) 个节点的树,节点从 \(1\) 到 \(n\) 编号,并且每个节点要么是白色,要么是黑色。
对于树上的一条简单路径(不经过重复节点的路径),小杨认为它是美丽的当且仅当路径上相邻节点的颜色均不相同。例如下图,其中节点 \(1\) 和节点 \(4\) 是黑色,其余节点是白色,路径 \(2-1-3-4\) 是美丽路径,而路径 \(2-1-3-5\) 不是美丽路径(相邻节点 \(3\) 和 \(5\) 颜色相同)。

对于树上的一条简单路径,小杨认为它的长度是路径包含节点的数量。小杨想知道最长的美丽路径的长度是多少。
【输入】
第一行包含一个正整数 \(n\),代表节点数量。
第二行包含 \(n\) 个整数 \(c_1,c_2,\dots,c_n\),代表每个节点的颜色,如果 \(c_i=0\),代表节点 \(i\) 为白色,如果 \(c_i=1\),代表节点 \(i\) 为黑色。
之后 \(n-1\) 行,每行包含两个正整数 \(u_i,v_i\),代表存在一条连接节点 \(u_i\) 和节点 \(v_i\) 的边。
【输出】
输出一个整数,代表最长美丽路径的长度。
【输入样例】
5
1 0 0 1 0
1 2
3 5
4 3
1 3
【输出样例】
4
【核心思想】
-
问题分析:给定一棵 \(n\) 个节点的树,每个节点为黑色或白色。求最长"美丽路径"长度——即路径上相邻节点颜色均不相同的简单路径的节点数最大值。本质是一个带颜色约束的树直径问题,需要找到颜色交替的最长链。
-
算法选择:
- 树形DP(换根思想的简化版):对每个节点 \(u\),维护以 \(u\) 为端点的最长和次长"美丽路径"长度,通过子树信息合并计算经过 \(u\) 的最长路径
- 关键观察:最长美丽路径要么完全在某棵子树内(递归求解),要么经过当前节点 \(u\) 连接两个不同子树的路径(\(f1[u] + f2[u]\))
-
关键步骤:
- 建图与初始化:用邻接表存储树,读取节点颜色 \(c[1..n]\)
- DFS树形DP(参数 \(u\) 为当前节点,\(\text{fa}\) 为父节点):
- 遍历子节点:对于 \(u\) 的每个邻接节点 \(j\)(跳过父节点 \(\text{fa}\)):
- 递归计算子树信息:\(\text{DFS}(j, u)\)
- 颜色判定转移:若 \(c[u] \neq c[j]\)(颜色不同,可延续美丽路径),则 \(t = f1[j] + 1\);否则 \(t = 0\)(路径断裂)
- 维护最长与次长:将 \(t\) 与 \(f1[u]\)、\(f2[u]\) 比较更新,保持 \(f1[u] \geq f2[u]\)
- 更新全局答案:\(\text{ans} = \max(\text{ans}, f1[u] + f2[u])\),表示经过 \(u\) 的最长美丽路径(由两条子链拼接而成)
- 遍历子节点:对于 \(u\) 的每个邻接节点 \(j\)(跳过父节点 \(\text{fa}\)):
- 结果转换:树形DP中 \(f1, f2\) 记录的是边数,最终答案为 \(\text{ans} + 1\)(转换为节点数)
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),每个节点只访问一次,每条边只处理一次
- 空间复杂度:\(O(n)\),邻接表和DP数组空间
-
树形DP求带约束直径的核心思想:
- 子树信息上传:\(f1[u]\) 表示以 \(u\) 为端点、向下延伸的最长美丽路径的边数,通过子节点的 \(f1[j]\) 转移得到
- 颜色断点处理:当 \(c[u] = c[j]\) 时,路径无法延续,强制 \(t = 0\),确保美丽路径的相邻节点颜色交替
- 直径拼接原理:经过 \(u\) 的最长路径由 \(u\) 的两个不同子树中的最长链拼接而成,因此维护最长和次长两个值
- 边数转节点数:DP过程中以边数计算,最终答案加 \(1\) 转换为路径包含的节点数量
- 适用于树上带约束的最长链、直径变形、颜色/权值限制的路径问题
【算法标签】
普及 #树形DP
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = N * 2;
int n; // 节点数量
int c[N]; // c[i] 表示节点i的颜色
int f1[N]; // f1[i]: 以节点i为起点的最长同色路径长度
int f2[N]; // f2[i]: 以节点i为起点的次长同色路径长度
int ans; // 最终答案(路径上的节点数-1,即最大长度)
int h[N], e[M], ne[M], idx; // 邻接表存储树
// 添加无向边
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
// 深度优先搜索
// u: 当前节点
// fa: 父节点
void dfs(int u, int fa)
{
// 遍历所有邻接节点
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i]; // 邻接节点
if (j == fa) // 跳过父节点
{
continue;
}
// 递归处理子树
dfs(j, u);
// 计算从当前节点u到子节点j的路径长度
// 如果u和j颜色不同,路径长度为f1[j]+1,否则为0
int t = (c[u] != c[j] ? f1[j] + 1 : 0);
// 更新f1[u]和f2[u]
if (t > f1[u])
{
f2[u] = f1[u]; // 原来的最大值变为次大值
f1[u] = t; // 更新最大值
}
else if (t > f2[u])
{
f2[u] = t; // 更新次大值
}
}
// 更新答案:考虑经过节点u的最长同色路径
// 注意:这里计算的是路径上的边数,而不是节点数
ans = max(ans, f1[u] + f2[u]);
}
int main()
{
cin >> n;
// 初始化邻接表
memset(h, -1, sizeof(h));
idx = 0;
// 读取每个节点的颜色
for (int i = 1; i <= n; i++)
{
cin >> c[i];
}
// 读取树的边
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
// 从节点1开始DFS
dfs(1, 0);
// 输出结果:最长同色路径的节点数 = 边数 + 1
cout << ans + 1 << endl;
return 0;
}
【运行结果】
5
1 0 0 1 0
1 2
3 5
4 3
1 3
4
浙公网安备 33010602011771号