题目描述

给出一棵n个点的树,以及m次操作,每次操作从起点向终点以每秒一条边的速度移动(初始时刻为0),最后对于每个点询问有多少次操作在经过该点的时刻为某值。

输入

第一行有两个整数N和M 。其中N代表树的结点数量, 同时也是观察员的数量, M代表玩家的数量。
接下来n-1 行每行两个整数U和V ,表示结点U 到结点V 有一条边。
接下来一行N 个整数,其中第个整数为Wj , 表示结点出现观察员的时间。
接下来 M行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证 。
1<=Si,Ti<=N,0<=Wj<=N

输出

输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。

样例输入

6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6

样例输出

2 0 0 1 1 1


题解

自己YY出的权值线段树合并

传说中的Noi没有p2016的神题。。。写出1个log的算法好像也不是很难。。。

先说下我的方法:

把每次操作拆成两部分:x->lca和lca->y,可以发现第一部分经过的点的 时刻+深度 是一个定值,第二部分经过的点的 时刻-深度 也是一个定值。所以问题转化为:给一条链上的每个点加1个定值、询问每个点某值的出现次数。

以第一种操作为例:由于只有1次总询问,因此可以差分打标记来实现修改,即在x处给权值标记+1,fa[lca]处给权值标记-1。然后处理询问时要求的就是子树的所有标记之和。可以使用线段树自底向上合并的方法来实现维护子树的所有标记之和。

总的时间复杂度为$O((n+m)\log n)$。

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <algorithm>
  4. #define N 300010
  5. #define lson l , mid , ls[x]
  6. #define rson mid + 1 , r , rs[x]
  7. using namespace std;
  8. int n , head[N] , to[N << 1] , next[N << 1] , cnt , fa[N][20] , deep[N] , log[N];
  9. int w[N] , ra[N] , rb[N] , ls[N << 6] , rs[N << 6] , si[N << 6] , tot , ans[N];
  10. void update(int p , int a , int l , int r , int &x)
  11. {
  12. if(!x) x = ++tot;
  13. si[x] += a;
  14. if(l == r) return;
  15. int mid = (l + r) >> 1;
  16. if(p <= mid) update(p , a , lson);
  17. else update(p , a , rson);
  18. }
  19. int merge(int x , int y)
  20. {
  21. if(!x) return y;
  22. if(!y) return x;
  23. si[x] += si[y];
  24. ls[x] = merge(ls[x] , ls[y]);
  25. rs[x] = merge(rs[x] , rs[y]);
  26. return x;
  27. }
  28. int query(int p , int l , int r , int x)
  29. {
  30. if(!x) return 0;
  31. if(l == r) return si[x];
  32. int mid = (l + r) >> 1;
  33. if(p <= mid) return query(p , lson);
  34. else return query(p , rson);
  35. }
  36. inline void add(int x , int y)
  37. {
  38. to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
  39. }
  40. void dfs(int x)
  41. {
  42. int i;
  43. for(i = 1 ; (1 << i) <= deep[x] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1];
  44. for(i = head[x] ; i ; i = next[i])
  45. if(to[i] != fa[x][0])
  46. fa[to[i]][0] = x , deep[to[i]] = deep[x] + 1 , dfs(to[i]);
  47. }
  48. inline int lca(int x , int y)
  49. {
  50. int i;
  51. if(deep[x] < deep[y]) swap(x , y);
  52. for(i = log[deep[x] - deep[y]] ; ~i ; i -- )
  53. if(deep[x] - deep[y] >= (1 << i))
  54. x = fa[x][i];
  55. if(x == y) return x;
  56. for(i = log[deep[x]] ; ~i ; i -- )
  57. if(deep[x] >= (1 << i) && fa[x][i] != fa[y][i])
  58. x = fa[x][i] , y = fa[y][i];
  59. return fa[x][0];
  60. }
  61. void solve(int x)
  62. {
  63. int i;
  64. for(i = head[x] ; i ; i = next[i])
  65. if(to[i] != fa[x][0])
  66. solve(to[i]) , ra[x] = merge(ra[x] , ra[to[i]]) , rb[x] = merge(rb[x] , rb[to[i]]);
  67. ans[x] = query(w[x] + deep[x] , 0 , n << 1 , ra[x]) + query(w[x] - deep[x] , -n , n , rb[x]);
  68. }
  69. int main()
  70. {
  71. int m , i , x , y , z;
  72. scanf("%d%d" , &n , &m);
  73. for(i = 2 ; i <= n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x) , log[i] = log[i >> 1] + 1;
  74. dfs(1);
  75. for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &w[i]);
  76. for(i = 1 ; i <= m ; i ++ )
  77. {
  78. scanf("%d%d" , &x , &y) , z = lca(x , y);
  79. update(deep[x] , 1 , 0 , n << 1 , ra[x]) , update(deep[x] , -1 , 0 , n << 1 , ra[fa[z][0]]);
  80. update(deep[x] - 2 * deep[z] , 1 , -n , n , rb[y]) , update(deep[x] - 2 * deep[z] , -1 , -n , n , rb[z]);
  81. }
  82. solve(1);
  83. for(i = 1 ; i < n ; i ++ ) printf("%d " , ans[i]);
  84. printf("%d" , ans[n]);
  85. return 0;
  86. }

写完发现自己naive了。。。

事实上,求的是子树内的标记和。和是存在逆运算的(差),可以转化为 包含子树的部分与不包含子树的部分的差 来求出。

具体地,在递归子树之前的答案为a,递归子树之后的答案为b,那么b-a就是子树内的答案。

所以可以直接使用递归前后的差作为答案。此时不再需要单独维护子树,所以可以使用桶来维护答案,直接使用两次桶里的值作差即可。这个部分的复杂度降为了$O(n)$。

于是只需要在线性时间内求出每次操作的LCA即可做到线性复杂度。惜哉我不会写Tarjan离线LCA,于是这一部分的代码也没有补上。。。

所以贴一个倍增LCA+桶的代码吧(代码中细节还是不少的,比如桶的下标不能为负等等):

  1. #include <cstdio>
  2. #include <vector>
  3. #define N 300010
  4. #define lson l , mid , ls[x]
  5. #define rson mid + 1 , r , rs[x]
  6. using namespace std;
  7. vector<int> va[N] , vb[N];
  8. int n , head[N] , to[N << 1] , next[N << 1] , cnt , fa[N][20] , deep[N] , log[N];
  9. int w[N] , ta[N << 1] , tb[N << 1] , ans[N];
  10. inline void add(int x , int y)
  11. {
  12. to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
  13. }
  14. void dfs(int x)
  15. {
  16. int i;
  17. for(i = 1 ; (1 << i) <= deep[x] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1];
  18. for(i = head[x] ; i ; i = next[i])
  19. if(to[i] != fa[x][0])
  20. fa[to[i]][0] = x , deep[to[i]] = deep[x] + 1 , dfs(to[i]);
  21. }
  22. inline int lca(int x , int y)
  23. {
  24. int i;
  25. if(deep[x] < deep[y]) swap(x , y);
  26. for(i = log[deep[x] - deep[y]] ; ~i ; i -- )
  27. if(deep[x] - deep[y] >= (1 << i))
  28. x = fa[x][i];
  29. if(x == y) return x;
  30. for(i = log[deep[x]] ; ~i ; i -- )
  31. if(deep[x] >= (1 << i) && fa[x][i] != fa[y][i])
  32. x = fa[x][i] , y = fa[y][i];
  33. return fa[x][0];
  34. }
  35. void solve(int x)
  36. {
  37. int i;
  38. ans[x] -= ta[w[x] + deep[x] + 1] + tb[w[x] - deep[x] + n];
  39. for(i = 0 ; i < (int)va[x].size() ; i ++ )
  40. {
  41. if(va[x][i] > 0) ta[va[x][i]] ++ ;
  42. else ta[-va[x][i]] -- ;
  43. }
  44. for(i = 0 ; i < (int)vb[x].size() ; i ++ )
  45. {
  46. if(vb[x][i] > 0) tb[vb[x][i]] ++ ;
  47. else tb[-vb[x][i]] -- ;
  48. }
  49. for(i = head[x] ; i ; i = next[i])
  50. if(to[i] != fa[x][0])
  51. solve(to[i]);
  52. ans[x] += ta[w[x] + deep[x] + 1] + tb[w[x] - deep[x] + n];
  53. }
  54. int main()
  55. {
  56. int m , i , x , y , z;
  57. scanf("%d%d" , &n , &m);
  58. for(i = 2 ; i <= n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x) , log[i] = log[i >> 1] + 1;
  59. dfs(1);
  60. for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &w[i]);
  61. for(i = 1 ; i <= m ; i ++ )
  62. {
  63. scanf("%d%d" , &x , &y) , z = lca(x , y);
  64. va[x].push_back(deep[x] + 1) , va[fa[z][0]].push_back(-deep[x] - 1);
  65. vb[y].push_back(n + deep[x] - 2 * deep[z]) , vb[z].push_back(2 * deep[z] - deep[x] - n);
  66. }
  67. solve(1);
  68. for(i = 1 ; i < n ; i ++ ) printf("%d " , ans[i]);
  69. printf("%d" , ans[n]);
  70. return 0;
  71. }

【bzoj4719】[Noip2016]天天爱跑步 权值线段树合并的更多相关文章

  1. [NOIP2016]天天爱跑步(树上差分+线段树合并)

    将每个人跑步的路径拆分成x->lca,lca->y两条路径分别考虑: 对于在点i的观察点,这个人(s->t)能被观察到的充要条件为: 1.直向上的路径:w[i]=dep[s]-dep ...

  2. B20J_2733_[HNOI2012]永无乡_权值线段树合并

    B20J_2733_[HNOI2012]永无乡_权值线段树合并 Description:n座岛,编号从1到n,每座岛都有自己的独一无二的重要度,按照重要度可以将这n座岛排名,名次用1到 n来表示.某些 ...

  3. 【bzoj1977】[BeiJing2010组队]次小生成树 Tree 最小生成树+权值线段树合并

    题目描述 求一张图的严格次小生成树的边权和,保证存在. 输入 第一行包含两个整数N 和M,表示无向图的点数与边数. 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z ...

  4. 【bzoj2212】[Poi2011]Tree Rotations 权值线段树合并

    原文地址:http://www.cnblogs.com/GXZlegend/p/6826614.html 题目描述 Byteasar the gardener is growing a rare tr ...

  5. luogu3224 永无乡(动态开点,权值线段树合并)

    luogu3224 永无乡(动态开点,权值线段树合并) 永无乡包含 n 座岛,编号从 1 到 n ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n 座岛排名,名次用 1 到 n 来表示.某些 ...

  6. 【bzoj4399】魔法少女LJJ 并查集+权值线段树合并

    题目描述 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ已经觉得自己见过世界上的所有稀奇古怪的事情了LJJ感叹道“这里真是个迷人的绿色世界,空气清新.淡雅,到处散发着醉人的奶浆味: ...

  7. 【bzoj3307】雨天的尾巴 权值线段树合并

    题目描述 N个点,形成一个树状结构.有M次发放,每次选择两个点x,y,对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. 输入 第一行数字N,M接下来 ...

  8. HDU-6704 K-th occurrence (后缀自动机father树上倍增建权值线段树合并)

    layout: post title: HDU-6704 K-th occurrence (后缀自动机father树上倍增建权值线段树合并) author: "luowentaoaa&quo ...

  9. BZOJ2733/LG3324 「HNOI2014」永无乡 权值线段树合并

    问题描述 BZOJ2733 LG3224 题解 对于每个结点建立一棵权值线段树. 查询操作就去查询第 \(k\) 大,合并操作就合并两颗权值线段树. 并查集维护连通性. 同时 STO hkk,zcr, ...

随机推荐

  1. securecrt颜色设置

    https://blog.csdn.net/zq710727244/article/details/53909801

  2. jquery ajax参数

    //默认请求参数 var _options = { url: null, // 请求连接地址 type: 'GET', // 请求类型(get,post) data: null, // post时请求 ...

  3. docker-compose 构建mongodb并导入基础数据示例

    使用docker-compose构建mongodb服务并导入基础数据示例. 1.文件目录结构 ——mongo/ |——docker-compose.yml |——mongo-Dockerfile |— ...

  4. PHP提取奇数或偶数下标元素

    该功能主要用到 array_filter() 函数,这个函数可以用回调函数过滤数组中的单元.用法: array array_filter ( array $array [, callable $cal ...

  5. Linux编译移植Qt5的环境_Xillinx的ZYNQ平台

    Linux编译Qt环境 2017年的十一假期,足不出户,一个人在教研室里面搞Qt的移植.我手里面有Samsung的CortexA8,Samsung的 CortexA53还有Ti的Sitara系列的AM ...

  6. Pandas基本命令

    关键缩写和包导入 在这个速查手册中,我们使用如下缩写: df:任意的Pandas DataFrame对象 同时我们需要做如下的引入: import pandas as pd 创建测试对象 import ...

  7. C语言数组篇(五)多级指针和二维数组指针的区别

    多级指针   以二级指针为例 二级指针的由来是 指针数组 的指针形式. int *p[10] 读取的顺序是 p[] --> 10个空间的数组 * p[] --> 这10个空间的数组里面存放 ...

  8. B1016 部分A+B (15分)

    B1016 部分A+B (15分) 输入格式: 输入在一行中依次给出 A.DA.B.DB,中间以空格分隔,其中 \(0<A,B<10^10\). 输出格式: 在一行中输出 PA+PB的值. ...

  9. ElasticSearch 之 Client

    在使用ElasticSearch的时候,我们需要与Cluster通信,Java版本的API提供了几种方式来构造Client,进而通过Client操作Cluster.   1)使用Node与clusto ...

  10. Android stadio 插件推荐--ok gradle

    今天发现了一个好玩的插件,对于想要知道依赖怎么写的同学很有帮助. 写这篇文章的意义在于,以后我忘了的话,可以自己在博客中找到. 上地址: https://github.com/scana/ok-gra ...