(题面来自Luogu)

题目描述

奶牛们又一次试图创建一家创业公司,还是没有从过去的经验中吸取教训--牛是可怕的管理者!

为了方便,把奶牛从 1⋯N(1≤N≤100,000) 编号,把公司组织成一棵树,1 号奶牛作为总裁(这棵树的根节点)。除了总裁以外的每头奶牛都有一个单独的上司(它在树上的 “双亲结点”)。所有的第 i 头牛都有一个不同的能力指数 p(i),描述了她对其工作的擅长程度。如果奶牛 i 是奶牛 j 的祖先节点(例如,上司的上司的上司),那么我们我们把奶牛 j 叫做 i 的下属。

不幸地是,奶牛们发现经常发生一个上司比她的一些下属能力低的情况,在这种情况下,上司应当考虑晋升她的一些下属。你的任务是帮助奶牛弄清楚这是什么时候发生的。简而言之,对于公司的中的每一头奶牛 i,请计算其下属 j 的数量满足 p(j)>p(i)。

输入格式

输入的第一行包括一个整数 N。

接下来的 N 行包括奶牛们的能力指数 p(1)⋯p(N). 保证所有数互不相同,在区间 1⋯⋯1e9 之间。

接下来的 N−1 行描述了奶牛 2⋯⋯N 的上司(父节点)的编号。再次提醒,1 号奶牛作为总裁,没有上司。

输出格式

输出包括 N 行。输出的第 i 行应当给出有多少奶牛 i 的下属比奶牛 i 能力高。

  同样是大规模统计子树信息的问题,这题并不能拿启发式合并来做,因为子树内的要求信息和根节点的信息要求不同,与根本身的信息有关。换句话说,无法把重儿子的信息直接合并给根。

  线段树合并的一般对象是两棵动态开点的权值线段树。权值线段树动态开点可以只见出建出没有被插入过的位置,时空复杂度O(nloginf),可以节省很大的空间。查询时避开空节点即可。

代码:

  1. void modify(int &nd, int l, int r, int x) {
  2. if (!nd) nd = ++tot;
  3. if (l == r) {
  4. ++cnt[nd];
  5. return;
  6. }
  7. if (x <= mid) modify(lc[nd], l, mid, x);
  8. else modify(rc[nd], mid + 1, r, x);
  9. update(nd);
  10. }

  动态开点是不需要离散化序列的,因为没有被访问到的值不会被建出来。不过在题目条件允许的情况下,离散化可以把线段树本身的空间和时间的复杂度都降到O(nlogn),而离散化本身的复杂度又很小,可以视题目尽量加上。

  线段树合并的过程很简单,它利用了动态开点线段树部分节点为空的性质,在递归的过程中直接暴力合并两棵线段树对应节点的信息,遇到空节点直接返回。合并函数写法有两种,前者通过新建线段树保留旧线段树的信息,后者直接利用原有节点,不需要额外开辟空间。静态子树统计的问题中每个节点返回后不会被再次访问,一般采用省空间的写法。

写法1:

  1. int merge(int &u, int v) {
  2. if (!v) return u;
  3. if (!u) return v;
  4. int nd = ++tot;
  5. cnt[nd] = cnt[lc[nd]] + cnt[rc[nd]];//把v的信息并到u上
  6. lc[nd] = merge(lc[u], lc[v]);
  7. rc[nd] = merge(rc[u], rc[v]);
  8. return nd;
  9. }

写法2:

  1. void merge(int &u, int v) {
  2. if (!u || !v) {//存在一个空节点
  3. u += v;//直接把u指向非空的那个点
  4. return;
  5. }
  6. seg[u].cnt += seg[v].cnt;//把v的信息并到u上
  7. merge(lc[u], lc[v]);
  8. merge(rc[u], rc[v]));
  9. return;
  10. }

  时间复杂度证明:假设最初存在的线段树共有O(nlogn)级别的节点数。每一次合并操作至少会减少一个原有节点,所以总复杂度的上界就是O(nlogn)的。

  线段树合并常数比树上启发式合并大一些,能承受的范围大概在1e6左右。不过由于每个节点合并起来得到的线段树是分开统计的,它更普适于类似的子树统计问题。

  其实该题中的查询子树权值信息和合并操作,用树状数组就可以维护。更神仙的做法是暴上主席树……但是作为线段树合并的模板还是要认真打的……

  考虑对每个叶子节点维护一棵权值线段树,然后递归地合并一个节点u的每一个子节点中的线段树,在这棵树上查询比能力值score[u]大的节点数就等价于查询[score[u] + 1, inf]的区间和。然后把score[u]插入该线段树中即可返回。由于只需要比较大小关系,可以做离散化来优化时间复杂度。

代码:

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <algorithm>
  5. #define BUG puts("$$$")
  6. #define maxn 100010
  7. template <typename T>
  8. void read(T &x) {
  9. x = 0;
  10. char ch = getchar();
  11. //  int f = 1;
  12. while (!isdigit(ch)) {
  13. //      if (ch == '-') f = -1;
  14. ch = getchar();
  15. }
  16. while (isdigit(ch)) {
  17. x = x * 10 + (ch ^ 48);
  18. ch = getchar();
  19. }
  20. //  x *= f;
  21. }
  22. using namespace std;
  23. int n, N;
  24. int head[maxn], top;
  25. struct E {
  26. int to, nxt;
  27. } edge[maxn << 1];
  28. inline void insert(int u, int v) {
  29. edge[++top] = (E) {v, head[u]};
  30. head[u] = top;
  31. }
  32. int score[maxn];
  33. namespace Segment_tree {
  34. #define mid ((l + r) >> 1)
  35. int tot = 0;
  36. struct node {
  37. int cnt, lc, rc;
  38. node(): cnt(0), lc(0), rc(0) {}
  39. } seg[maxn << 4];
  40. #define lc(nd) seg[nd].lc
  41. #define rc(nd) seg[nd].rc
  42. void update(int nd) {
  43. seg[nd].cnt = seg[lc(nd)].cnt + seg[rc(nd)].cnt;
  44. }
  45. void modify(int &nd, int l, int r, int x) {
  46. if (!nd) nd = ++tot;
  47. if (l == r) {
  48. ++seg[nd].cnt;
  49. return;
  50. }
  51. if (x <= mid) modify(lc(nd), l, mid, x);
  52. else modify(rc(nd), mid + 1, r, x);
  53. update(nd);
  54. }
  55. int query(int nd, int l, int r, int ql, int qr) {
  56. if (!nd) return 0;
  57. if (l >= ql && r <= qr)
  58. return seg[nd].cnt;
  59. if (l > qr || r < ql)
  60. return 0;
  61. return query(lc(nd), l, mid, ql, qr) + query(rc(nd), mid + 1, r, ql, qr);
  62. }
  63. void merge(int &u, int v) {
  64. if (!u || !v) {
  65. u += v;
  66. return;
  67. }
  68. seg[u].cnt += seg[v].cnt;
  69. merge(lc(u), lc(v));
  70. merge(rc(u), rc(v));
  71. return;
  72. }
  73. int ans[maxn], rt[maxn];
  74. void solve(int u) {//核心代码
  75. for (int i = head[u]; i; i = edge[i].nxt) {
  76. int v = edge[i].to;
  77. solve(v);
  78. merge(rt[u], rt[v]);
  79. }
  80. ans[u] = query(rt[u], 1, N, score[u] + 1, N);
  81. modify(rt[u], 1, N, score[u]);
  82. return;
  83. }
  84. } using namespace Segment_tree;
  85. int st[maxn];
  86. int contra(int *a) {//离散化
  87. memcpy(st, a, sizeof(st));
  88. sort(st + 1, st + 1 + n);
  89. int len = unique(st + 1, st + 1 + n) - st - 1;
  90. for (int i = 1; i <= n; ++i)
  91. a[i] = lower_bound(st + 1, st + 1 + len, a[i]) - st;
  92. return len;
  93. }
  94. int main() {
  95. read(n);
  96. int u, v;
  97. for (int i = 1; i <= n; ++i)
  98. read(score[i]);
  99. N = contra(score);
  100. for (u = 2; u <= n; ++u) {
  101. read(v);
  102. insert(v, u);
  103. }
  104. solve(1);
  105. for (int i = 1; i <= n; ++i)
  106. printf("%d\n", ans[i]);
  107. return 0;
  108. }

【模板】【P3605】【USACO17JAN】Promotion Counting 晋升者计数——动态开点和线段树合并(树状数组/主席树)的更多相关文章

  1. 线段树合并 || 树状数组 || 离散化 || BZOJ 4756: [Usaco2017 Jan]Promotion Counting || Luogu P3605 [USACO17JAN]Promotion Counting晋升者计数

    题面:P3605 [USACO17JAN]Promotion Counting晋升者计数 题解:这是一道万能题,树状数组 || 主席树 || 线段树合并 || 莫队套分块 || 线段树 都可以写..记 ...

  2. 树状数组 P3605 [USACO17JAN]Promotion Counting晋升者计数

    P3605 [USACO17JAN]Promotion Counting晋升者计数 题目描述 奶牛们又一次试图创建一家创业公司,还是没有从过去的经验中吸取教训--牛是可怕的管理者! 为了方便,把奶牛从 ...

  3. 洛谷P3605 [USACO17JAN] Promotion Counting 晋升者计数 [线段树合并]

    题目传送门 Promotion Counting 题目描述 The cows have once again tried to form a startup company, failing to r ...

  4. 洛谷 P3605 [USACO17JAN]Promotion Counting晋升者计数

    题目描述 The cows have once again tried to form a startup company, failing to remember from past experie ...

  5. luogu P3605 [USACO17JAN]Promotion Counting晋升者计数

    题目链接 luogu 思路 可以说是线段树合并的练手题目吧 也没啥说的,就是dfs,然后合并... 看代码吧 错误 和写主席树错的差不多 都是变量写错.... 代码 #include <bits ...

  6. P3605 [USACO17JAN]Promotion Counting晋升者计数

    思路 线段树合并的板子.. 和子节点合并之后在值域线段树上查询即可 代码 #include <cstdio> #include <algorithm> #include < ...

  7. Luogu3605 [USACO17JAN]Promotion Counting晋升者计数

    Luogu3605 [USACO17JAN]Promotion Counting晋升者计数 给一棵 \(n\) 个点的树,点 \(i\) 有一个权值 \(a_i\) .对于每个 \(i\) ,求 \( ...

  8. [USACO17JAN]Promotion Counting晋升者计数

    题目描述 奶牛们又一次试图创建一家创业公司,还是没有从过去的经验中吸取教训--牛是可怕的管理者! 为了方便,把奶牛从 1 \cdots N(1 \leq N \leq 100, 000)1⋯N(1≤N ...

  9. 题解 P3605 【[USACO17JAN]Promotion Counting晋升者计数】

    这道题开10倍左右一直MLE+RE,然后尝试着开了20倍就A了...窒息 对于这道题目,我们考虑使用线段树合并来做. 所谓线段树合并,就是把结构相同的线段树上的节点的信息合在一起,合并的方式比较类似左 ...

随机推荐

  1. Rancher 2.5特性解读丨更简单友好的API和Dashboard

    本文来自Rancher Labs 关注我们,看K8S干货教程 作者简介 张智博,Rancher中国研发与产品总监.7年云计算领域经验,一直活跃在研发一线,经历了OpenStack到Kubernetes ...

  2. HTTP协议(2)

    HTTP协议主要是有HTTP请求报文和HTTP响应报文组成的. HTTP请求报文主要分为四个部分: 第一部分:请求行.独占一行,由请求方法.请求url以及协议/版本组成: 第二部分:请求头,第二行到第 ...

  3. plc模拟量采集模块的作用

    在工业控制中,某些输入量(如压力.温度.流量.转速等)是连续变化的模拟量,某些执行机构(如伺服电动机.调节阀.记录仪等)要求PLC输出模拟信号,而PLC的CPU只能处理数字量.模拟量首先被传感器和变送 ...

  4. LTE DTU和4G DTU有什么不同

    其实4G DTU和LTE DTU从本质上来说是没有什么区别的,只是使用的运营商不同,设备的编号会有不同,都是属于DTU设备. LTE是baiLong Term Evolution(长期演进)的缩写.3 ...

  5. eyou去版权

    公司老板要求做一个自己门户网站,苦于公司自己又没有开发相应的cms内容管理系统,找了一个星期,综合各方面的考虑,决定选择eyoucms来搭建,经过快速安装,来到了简单干净的后台. 现将使用体会记录如下 ...

  6. 关于ubuntu出现的一些问题的解决方法

    1. (1)现象: dpkg: 处理软件包 linux-image-4.15.0-36-generic (--configure)时出错: 子进程 已安装 post-installation 脚本 返 ...

  7. pc端兼容IE9及以上版本

    最近业务部门反映我们商城的兼容性不是很好,尤其是在IE浏览器上,经过调研,我们决定对IE9及以上版本的IE内核浏览器进行主流程测试,发现有哪些功能在IE9上不兼容 一.CSS兼容性 如下图所示 使用了 ...

  8. .NET 5 中的隐藏特性

    前言 双十一当天 .NET 5 正式发布带来了很多的新特性和改进,个人觉得非常香,并且花了 10 分钟时间就把自己的 4 个 .NET Core 3.1 的项目升级到了 .NET 5,堪称无痛. 但是 ...

  9. String字符串加号的作用与基本数据类型加号的作用的区别

    1 public static void main(String args[] ){ 2 String Str="hellow"; 3 int num=110; 4 char c= ...

  10. git引入_版本控制介绍

    八个字形容git技术: 公司必备,一定要会 一.git概念: git是一个免费的,开源的分布式版本控制系统,可以快速高效的处理从小型到大型的项目 二.什么是版本控制: 版本控制是一种一个记录一个或若个 ...