本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。
欢迎大家订阅我的专栏:算法题解:C++与Python实现!
附上汇总贴:算法竞赛备考冲刺必刷题(C++) | 汇总
【题目来源】
洛谷:P11251 [GESP202409 八级] 美丽路径 – 洛谷
【题目描述】
小杨有一棵包含
n
n
n 个节点的树,节点从
1
1
1 到
n
n
n 编号,并且每个节点要么是白色,要么是黑色。
对于树上的一条简单路径(不经过重复节点的路径),小杨认为它是美丽的当且仅当路径上相邻节点的颜色均不相同。例如下图,其中节点
1
1
1 和节点
4
4
4 是黑色,其余节点是白色,路径
2
−
1
−
3
−
4
2-1-3-4
2−1−3−4 是美丽路径,而路径
2
−
1
−
3
−
5
2-1-3-5
2−1−3−5 不是美丽路径(相邻节点
3
3
3 和
5
5
5 颜色相同)。

对于树上的一条简单路径,小杨认为它的长度是路径包含节点的数量。小杨想知道最长的美丽路径的长度是多少。
【输入】
第一行包含一个正整数
n
n
n,代表节点数量。
第二行包含
n
n
n 个整数
c
1
,
c
2
,
…
,
c
n
c_1,c_2,\\dots,c_n
c1,c2,…,cn,代表每个节点的颜色,如果
c
i
=
0
c_i=0
ci=0,代表节点
i
i
i 为白色,如果
c
i
=
1
c_i=1
ci=1,代表节点
i
i
i 为黑色。
之后
n
−
1
n-1
n−1 行,每行包含两个正整数
u
i
,
v
i
u_i,v_i
ui,vi,代表存在一条连接节点
u
i
u_i
ui 和节点
v
i
v_i
vi 的边。
【输出】
输出一个整数,代表最长美丽路径的长度。
【输入样例】
5
1 0 0 1 0
1 2
3 5
4 3
1 3
【输出样例】
4
【核心思想】
问题分析:给定一棵
n
n
n 个节点的树,每个节点为黑色或白色。求最长"美丽路径"长度——即路径上相邻节点颜色均不相同的简单路径的节点数最大值。本质是一个带颜色约束的树直径问题,需要找到颜色交替的最长链。
算法选择:
- 树形DP(换根思想的简化版):对每个节点
u
u
u,维护以u
u
u 为端点的最长和次长"美丽路径"长度,通过子树信息合并计算经过u
u
u 的最长路径 - 关键观察:最长美丽路径要么完全在某棵子树内(递归求解),要么经过当前节点
u
u
u 连接两个不同子树的路径(f
1
[
u
]
+
f
2
[
u
]
f1[u] + f2[u]
f1[u]+f2[u])
关键步骤:
- 建图与初始化:用邻接表存储树,读取节点颜色
c
[
1..
n
]
c[1..n]
c[1..n] - DFS树形DP(参数
u
u
u 为当前节点,fa
\\text{fa}
fa 为父节点):- 遍历子节点:对于
u
u
u 的每个邻接节点j
j
j(跳过父节点fa
\\text{fa}
fa):- 递归计算子树信息:
DFS
(
j
,
u
)
\\text{DFS}(j, u)
DFS(j,u) - 颜色判定转移:若
c
[
u
]
≠
c
[
j
]
c[u] \\neq c[j]
c[u]=c[j](颜色不同,可延续美丽路径),则t
=
f
1
[
j
]
+
1
t = f1[j] + 1
t=f1[j]+1;否则t
=
0
t = 0
t=0(路径断裂) - 维护最长与次长:将
t
t
t 与f
1
[
u
]
f1[u]
f1[u]、f
2
[
u
]
f2[u]
f2[u] 比较更新,保持f
1
[
u
]
≥
f
2
[
u
]
f1[u] \\geq f2[u]
f1[u]≥f2[u]
- 递归计算子树信息:
- 更新全局答案:
ans
=
max
(
ans
,
f
1
[
u
]
+
f
2
[
u
]
)
\\text{ans} = \\max(\\text{ans}, f1[u] + f2[u])
ans=max(ans,f1[u]+f2[u]),表示经过u
u
u 的最长美丽路径(由两条子链拼接而成)
- 遍历子节点:对于
- 结果转换:树形DP中
f
1
,
f
2
f1, f2
f1,f2 记录的是边数,最终答案为ans
+
1
\\text{ans} + 1
ans+1(转换为节点数)
时间/空间复杂度:
- 时间复杂度:
O
(
n
)
O(n)
O(n),每个节点只访问一次,每条边只处理一次 - 空间复杂度:
O
(
n
)
O(n)
O(n),邻接表和DP数组空间
树形DP求带约束直径的核心思想:
- 子树信息上传:
f
1
[
u
]
f1[u]
f1[u] 表示以u
u
u 为端点、向下延伸的最长美丽路径的边数,通过子节点的f
1
[
j
]
f1[j]
f1[j] 转移得到 - 颜色断点处理:当
c
[
u
]
=
c
[
j
]
c[u] = c[j]
c[u]=c[j] 时,路径无法延续,强制t
=
0
t = 0
t=0,确保美丽路径的相邻节点颜色交替 - 直径拼接原理:经过
u
u
u 的最长路径由u
u
u 的两个不同子树中的最长链拼接而成,因此维护最长和次长两个值 - 边数转节点数:DP过程中以边数计算,最终答案加
1
1
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


![打卡信奥刷题(3652)用C++实现信奥题 P12076 [OOI 2025] Cute Subsequences-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261026055333-6adeeaddd0c48-220x150.png)
![题解:洛谷 P2871 [USACO07DEC] Charm Bracelet S-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261026033949-6adecb8504bd0-220x150.png)