题解:洛谷 P2018 消息传递

【题目来源】

洛谷:P2018 消息传递 - 洛谷

【题目描述】

巴蜀国的社会等级森严,除了国王之外,每个人均有且只有一个直接上级,当然国王没有上级。如果 \(A\)\(B\) 的上级,\(B\)\(C\) 的上级,那么 \(A\) 就是 \(C\) 的上级。绝对不会出现这样的关系:\(A\)\(B\) 的上级,\(B\) 也是 \(A\) 的上级。

最开始的时刻是 \(0\),你要做的就是用 \(1\) 单位的时间把一个消息告诉某一个人,让他们自行散布消息。在任意一个时间单位中,任何一个已经接到消息的人,都可以把消息告诉他的一个直接上级或者直接下属。

现在,你想知道:

  1. 到底需要多长时间,消息才能传遍整个巴蜀国的所有人?
  2. 要使消息在传递过程中消耗的时间最短,可供选择的人有哪些?

【输入】

输入文件的第一行为一个整数 \(N\)\(N\le 1000\)),表示巴蜀国人的总数,假如人按照 \(1\)\(n\) 编上了号码,国王的编号是 \(1\)。第 \(2\) 行到第 \(N\) 行(共 \(N-1\) 行),每一行一个整数,第 \(i\) 行的整数表示编号为 \(i\) 的人直接上级的编号。

【输出】

文件输出共计两行:

  • 第一行为一个整数,表示最后一个人接到消息的最早时间。
  • 第二行有若干个数,表示可供选择人的编号,按照编号从小到大的顺序输出,中间用空格分开。

【输入样例】

8
1
1
3
4
4
4
3

【输出样例】

5
3 4 5 6 7

【算法标签】

普及+# #提高#

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 1005, M = N * 2;
int n;
int h[N], e[M], ne[M], idx;  // 邻接表
int cnt[N], ti[N], ans = 1e9, res;  // cnt: 子树大小, ti: 每个节点为根的结果, ans: 最小值, res: 当前根的结果

// 添加无向边
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 深度优先搜索
void dfs(int u, int fa)  // u: 当前节点, fa: 父节点
{
    cnt[u] = 1;  // 包括自己
    int maxn = 0, num = 0, t = 0;  // maxn: 最大子树大小, num: 最大子树个数, t: 子节点数
    for (int i = h[u]; ~i; i = ne[i])  // 遍历子节点
    {
        int v = e[i];
        if (v == fa)  // 跳过父节点
        {
            continue;
        }
        dfs(v, u);  // 递归处理子树
        t++;  // 子节点计数
        if (cnt[v] > maxn)  // 找到更大的子树
        {
            maxn = cnt[v];
            num = 1;
        }
        else if (cnt[v] == maxn)  // 相同大小的子树
        {
            num++;
        }
    }
    // if (fa) t++;  // 如果考虑父节点方向
    t = max(t, maxn + num - 1);  // 计算所需轮数
    cnt[u] += t;  // 更新当前节点的完成时间
    res = max(res, cnt[u]);  // 更新全局最大值
}
int main()
{
    cin >> n;  // 输入节点数
    memset(h, -1, sizeof(h));
    for (int i = 2; i <= n; i++)  // 输入边
    {
        int x;
        cin >> x;
        add(i, x), add(x, i);  // 建树
    }
    for (int i = 1; i <= n; i++)  // 以每个节点为根计算
    {
        memset(cnt, 0, sizeof(cnt));  // 重置计数数组
        res = 0;  // 重置结果
        dfs(i, 0);  // 从节点i开始DFS
        ti[i] = res;  // 记录以i为根的结果
        ans = min(ans, ti[i]);  // 更新最小值
    }
    cout << ans << endl;  // 输出最小结果
    for (int i = 1; i <= n; i++)  // 输出所有达到最小值的根节点
    {
        if (ti[i] == ans)
        {
            cout << i << " ";
        }
    }
    cout << endl;
    return 0;
}

【运行结果】

8
1
1
3
4
4
4
3
5
3 4 5 6 7
posted @ 2026-04-02 10:59  团爸讲算法  阅读(2)  评论(0)    收藏  举报