Forever Young

洛谷 P2018 消息传递

题目分析

贪心+树形DP

本来还以为要大费周折地换根,然后发现 \(n\) 很小,可以直接 \(O(n^2\log n)\) 枚举。

枚举每个节点作为根,用 \(f_x\) 表示走完以 \(x\) 为根的子树花费的最小时间。

那么如何更新呢?这个时候就要用到贪心的思想了。假设我们现在已经知道了 \(x\) 的儿子个数 \(tot\) 以及所有儿子 \(to\)\(f\) 值。那么 \(x\) 必定要把信息传给每一个儿子,所以要尽量早地把信息传给 \(f\) 值较大的儿子,因此要把所有儿子的 \(f\) 值从小到大排序,并得出如下 DP 方程:

\[f_{x}=\max\limits_{i=1}^{tot}(f_{to}+i) \]

最后的答案需要加 \(1\),因为最开始要花费 \(1\) 的时间把消息传播到根节点。

计算出以每个点为根的答案之后取最小值,再扫描一遍找可以作为根的点即可。

特别注意

在更新当前节点时,需要记录所有儿子的 \(f\) 值,如果要定义临时数组只能在函数内定义,因为在接下来的 dfs 过程中又用到了此数组,数组中的值会因此发生改变,所以不能在外面定义。

代码

#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int A = 1e3 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
  char c = getchar();
  int x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}

struct node { int to, nxt; } e[A << 1];
int n, ans[A], f[A], head[A], cnt = 0, res = inf; 

inline void add(int from, int to) {
  e[++cnt].to = to;
  e[cnt].nxt = head[from];
  head[from] = cnt;
}

bool cmp(int x, int y) {
  return x > y;
}

inline void dfs(int x, int fa) {
  int tot = 0, b[1000] = {0};
  
  for (int i = head[x]; i; i = e[i].nxt) {
    int to = e[i].to;
    if (to == fa) continue;
    dfs(to, x);
    b[++tot] = f[to];
  }
  sort(b + 1, b + 1 + tot, cmp);
  for (int i = 1; i <= tot; i++) 
    f[x] = max(f[x], b[i] + i);
}

int main() {
  n = read();
  for (int i = 2; i <= n; i++) {
    int x = read();
    add(x, i), add(i, x);
  }
  for (int i = 1; i <= n; i++) {
    memset(f, 0, sizeof(f));
    dfs(i, 0);
    res = min(f[i], res);
    ans[i] = f[i];
  }
  cout << res + 1 << '\n';
  for (int i = 1; i <= n; i++) 
    if (ans[i] == res) cout << i << " ";
  puts("");
  return 0;
}
posted @ 2020-11-16 18:09  Loceaner  阅读(132)  评论(0编辑  收藏  举报