题解:洛谷 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

【核心思想】

  1. 问题分析:给定一棵 \(n\) 个节点的树,每个节点为黑色或白色。求最长"美丽路径"长度——即路径上相邻节点颜色均不相同的简单路径的节点数最大值。本质是一个带颜色约束的树直径问题,需要找到颜色交替的最长链。

  2. 算法选择

    • 树形DP(换根思想的简化版):对每个节点 \(u\),维护以 \(u\) 为端点的最长和次长"美丽路径"长度,通过子树信息合并计算经过 \(u\) 的最长路径
    • 关键观察:最长美丽路径要么完全在某棵子树内(递归求解),要么经过当前节点 \(u\) 连接两个不同子树的路径(\(f1[u] + f2[u]\)
  3. 关键步骤

    • 建图与初始化:用邻接表存储树,读取节点颜色 \(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\) 的最长美丽路径(由两条子链拼接而成)
    • 结果转换:树形DP中 \(f1, f2\) 记录的是边数,最终答案为 \(\text{ans} + 1\)(转换为节点数)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),每个节点只访问一次,每条边只处理一次
    • 空间复杂度:\(O(n)\),邻接表和DP数组空间
  5. 树形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
posted @ 2026-08-28 15:53  团爸讲算法  阅读(6)  评论(0)    收藏  举报