题目

类型:树

image.png

解题思路

1、假定该树为多叉树。
2、题目给定的 parents 数组仅支持快速查找某个节点的父节点,为了方便遍历整棵树,先使用「邻接表」将图(树)建起来。
使用 DFS 预处理出 f 数组,其中 f[i] 代表以节点 i 为根节点的子树所包含的节点数量。

3、如何计算「删除某个节点 x 后,剩余连通块的数量,以及每个连通块的节点数量」,根据节点 x 是否为根节点进行分情况讨论:

  • 若 x 为根节点,删除后的连通块的数量为「x 的出边数量」,假定共有 k 条出边,根据题目定义,对应的 大小 为各个连通块的节点数量乘积:

统计最高分的节点数目 - 图2

  • 若 x 不是根节点,删除后的连通块的数量为「x 的出边数量 + 1」,其中 1 代指「以 x 节点的父节点所在的整体连通块」。

假定节点 x 共有 k 条出边,根据题目定义,对应的 大小 为「(各个连通块的节点数量乘积) 统计最高分的节点数目 - 图3 (x 节点的父节点所在的整体连通块大小)」,而「x 节点的父节点所在的整体连通块大小」,利用容斥原理可知为 f[root] - f[u] = n - f[u],含义为「从原树中减掉以节点 x 为根节点的子树」的部分,即最终 大小 为:
统计最高分的节点数目 - 图4

代码

  1. public class CountNodesWithTheHighestScore {
  2. static int N = 100010, M = N * 2;
  3. static int[] he = new int[N], e = new int[M], ne = new int[M];
  4. static int[] f = new int[N];
  5. int idx; //成员变量idx 默认初始值0,对边进行编号
  6. //建邻接图
  7. void add(int a, int b) {
  8. e[idx] = b; //当前idx边指向的节点
  9. ne[idx] = he[a]; //下一条边 若为-1则表示没有下一个了
  10. he[a] = idx++; //头结点
  11. }
  12. public int countHighestScoreNodes(int[] parents) {
  13. Arrays.fill(he, -1); //初始化让所有边都没有下一条
  14. int n = parents.length; //节点数目
  15. for (int i = 1; i < n; i++) add(parents[i], i); //建图
  16. dfs(0);//从根节点开始dfs,在搜索过程中存储当前节点的子树节点数目(含当前节点)
  17. long max = 0; //最坑的一个点,使用int会溢出,必须使用long
  18. int ans = 0; //最高得分计数
  19. for (int x = 0; x < n; x++) {
  20. long cur = 1;
  21. //删除节点x
  22. for (int i = he[x]; i != -1; i = ne[i]) cur *= f[e[i]]; //累乘删除的左右子树数量
  23. if (x != 0) cur *= n - f[x]; //如果不是根节点,还要再把上方剩余子树的n - f[x]个节点数量相乘
  24. if (cur > max) {
  25. //比之前的乘积大就替换,并把数量置1
  26. max = cur; ans = 1;
  27. } else if (cur == max) {
  28. //跟之前的最大值一样大,则数量+1
  29. ans++;
  30. }
  31. }
  32. return ans;
  33. }
  34. //递归函数
  35. int dfs(int u) {
  36. int ans = 1;//因为是依据边访问点,说明有一定有一个节点,所以至少都是1
  37. for (int i = he[u]; i != -1; i = ne[i]) ans += dfs(e[i]);//把子树的数目加上来
  38. f[u] = ans;//存储以u为根节点的树的节点数目
  39. return ans;
  40. }
  41. }