傻逼线段树,傻逼数剖

线段树

定义:

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。
有什么用?
线段树功能强大,支持区间求和,区间最大值,区间修改,单点修改等操作。
线段树的思想和分治思想很相像。
线段树的每一个节点都储存着一段区间[L…R]的信息,其中叶子节点L=R。它的大致思想是:将一段大区间平均地划分成2个小区间,每一个小区间都再平均分成2个更小区间……以此类推,直到每一个区间的L等于R(这样这个区间仅包含一个节点的信息,无法被划分)。通过对这些区间进行修改、查询,来实现对大区间的修改、查询。
这样一来,每一次修改、查询的时间复杂度都只为O(log2n)。
建树:
  1. void build(int l,int r,int i){//建树,为当前左边界,r为右边界,i为编号
  2. tree[i].l=l;//更新边界值
  3. tree[i].r=r;
  4. if(l==r){//如果是最底层的节点,sum就是本身
  5. tree[i].sum=input[l];
  6. return ;
  7. }
  8. int mid=(l+r)/2;
  9. build(l,mid,2*i);//左边部分建树
  10. build(mid+1,r,2*i+1);//右边部分建树
  11. tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%mod;//递归返回时更新sum值
  12. }

 区间修改:

  1. void add(int i,int L,int R,int k){//区间修改 ;
  2. if(tree[i].l>=L&&tree[i].r<=R){//被完全包含
  3. tree[i].sum=tree[i].sum+(tree[i].r-tree[i].l+1)*k; //修改区间
  4. tree[i].lazy+=k;//更新延迟标记
  5. return ;
  6. }
  7. push_down(i);//没有完全包含的话就先下传懒标记
  8. int mid=(tree[i].l+tree[i].r)/2;
  9. if(L<=mid) add(2*i,L,R,k);//左边有重合走左边
  10. if(mid<R) add(2*i+1,L,R,k);//右边有重合走右边
  11. //这里一定不能写mid<=R,不然会死循环
  12. tree[i].sum=tree[2*i].sum+tree[2*i+1].sum//更新sum值
  13. }

区间查询:

  1. int ask(int i,int L,int R){//区间查询
  2. if(tree[i].l>=L&&tree[i].r<=R) return tree[i].sum;//完全包含,道理同区间修改
  3. push_down(i);//没有完全包含就下传延迟标记
  4. int ans=0;
  5. int mid=(tree[i].l+tree[i].r)/2;
  6. if(mid>=L) ans=(ans+ask(2*i,L,R));
  7. if(mid<R) ans=(ans+ask(2*i+1,L,R));//记录答案
  8. return ans;
  9. }

下传延迟标记(push_down操作):

  1. void push_down(int i){//延迟标记下移
  2. if(tree[i].lazy){
  3. tree[2*i].sum=(tree[2*i].sum+(tree[2*i].r-tree[2*i].l+1)* tree[i].lazy%mod)%mod;
  4. tree[2*i+1].sum=(tree[2*i+1].sum+(tree[2*i+1].r-tree[2*i+1].l+1)* tree[i].lazy%mod)%mod;
  5. tree[2*i].lazy+=tree[i].lazy;
  6. tree[2*i+1].lazy+=tree[i].lazy;
  7. tree[i].lazy=0;
  8. }
  9. }

延迟标记的作用:

有些时候修改了也不一定会去查询,于是就打上延迟标记,需要的时候再下传。

板题:https://www.luogu.com.cn/problem/P3372

Code:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e5+5;
  4. int n,m;
  5. int type,x,y,k;
  6. long long input[N];
  7. struct node {
  8. int l;
  9. int r;
  10. long long sum;
  11. long long add;
  12. } tree[N*4];
  13.  
  14. void spread(int i) {
  15. if(tree[i].add) {
  16. tree[2*i].sum+=tree[i].add*(tree[2*i].r-tree[2*i].l+1);
  17. tree[2*i+1].sum+=tree[i].add*(tree[2*i+1].r-tree[2*i+1].l+1);
  18. tree[2*i].add+=tree[i].add;
  19. tree[2*i+1].add+=tree[i].add;
  20. tree[i].add=0;
  21. }
  22. }
  23.  
  24. void build(int i,int l,int r) {
  25. tree[i].l=l;
  26. tree[i].r=r;
  27. if(l==r) {
  28. tree[i].sum=input[l];
  29. return ;
  30. }
  31. int mid=(l+r)/2;
  32. build(2*i,l,mid);
  33. build(2*i+1,mid+1,r);
  34. tree[i].sum=tree[2*i].sum+tree[2*i+1].sum;
  35. }
  36.  
  37. void change(int i,int l,int r,int k) {
  38. if(l<=tree[i].l&&r>=tree[i].r) {
  39. tree[i].sum+=k*(tree[i].r-tree[i].l+1);
  40. tree[i].add+=k;
  41. return ;
  42. }
  43. spread(i);
  44. int mid=(tree[i].l+tree[i].r)/2;
  45. if(l<=mid)change(2*i,l,r,k);
  46. if(r>mid)change(2*i+1,l,r,k);
  47. tree[i].sum=tree[2*i].sum+tree[2*i+1].sum;
  48. }
  49.  
  50. long long check(int i,int l,int r) {
  51. if(l<=tree[i].l&&r>=tree[i].r) {
  52. return tree[i].sum;
  53. }
  54. spread(i);
  55. long long flag=0;
  56. int mid=(tree[i].l+tree[i].r)/2;
  57. if(l<=mid) flag+=check(2*i,l,r);
  58. if(r>mid) flag+=check(2*i+1,l,r);
  59. return flag;
  60. }
  61.  
  62. int main() {
  63. cin>>n>>m;
  64. for(int i=1; i<=n; i++) {
  65. cin>>input[i];
  66. }
  67. build(1,1,n);
  68. for(int i=1; i<=m; i++) {
  69. cin>>type;
  70. if(type==1) {
  71. cin>>x>>y>>k;
  72. change(1,x,y,k);
  73. }
  74. if(type==2) {
  75. cin>>x>>y;
  76. cout<<check(1,x,y)<<endl;
  77. }
  78. }
  79. return 0;
  80. }

好的接下来来到——

树剖

定义:我们以某种规则将一棵树剖分成若干条竖直方向上的链,每次维护时可以一次跳一条链、并借助一些强大的线性数据结构来维护(通常链的数量很少),这样就大大优化了时间复杂度,足以解决很多线性结构搬到树上的题目。

 变量:

  1. //son[N] 重儿子的编号,若没有重儿子则编号为-1
  2. //size[N] 子树的大小
  3. //f[N] 父亲节点的编号
  4. //d[N] 结点的深度
  5. //top[N] 所在链的链端
  6. //id[N] 经过重链剖分后的新编号
  7. //rk[N] 有rk[id[i]]=i

将树剖分成链的过程中,我们一共要进行两次dfs:

  1. void dfs1(int x,int fa,int depth){//x:当前结点 fa:父结点 depth:当前结点深度
  2. f[x]=fa;//更新父结点
  3. d[x]=depth;//更新深度
  4. size[x]=1;//子树大小初始化:根节点本身
  5. for(int i=head[x];i;i=nex[i]){
  6. int y=ver[i];
  7. if(y==fa) continue ;
  8. dfs1(y,x,depth+1);
  9. if(size[y]>size[son[x]]) son[x]=y;//更新重儿子
  10. size[x]+=size[y];//更新子树大小
  11. }
  12. return ;
  13. } //dfs1更新f[N],d[N],size[N],son[N]
  1. void dfs2(int u,int t){//u为当前结点,t为链段
  2. top[u]=t;
  3. id[u]=++cnt;
  4. rk[cnt]=u;//新的编号
  5. if(!son[u]) return ;
  6. dfs2(son[u],t);//优先遍历重儿子,使一条链上编号连续
  7. for(int i=head[u];i;i=nex[i]){
  8. int y=ver[i];
  9. if(y==son[u]||y==f[u]) continue;
  10. dfs2(y,y);//再建一条链
  11. }
  12. }//dfs2更新top[N],id[N],rk[N]

至此我们剖分的过程就已经完成了,现在让我们看看在题目中树链剖分有什么用:

题目:https://www.luogu.com.cn/problem/P3384

题目要求我们进行如下操作:

操作 1: 格式: 1 x y z 表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。(区间修改)

操作 2: 格式: 2 x y 表示求树从 x 到 y 结点最短路径上所有节点的值之和。(区间查询)

操作 3: 格式: 3 x z 表示将以 x 为根节点的子树内所有节点值都加上 z。(区间修改)

操作 4: 格式: 4 x 表示求以 x 为根节点的子树内所有节点值之和。(区间查询)

操作一:

  1. void func1(int x,int y,int k){//将树从 x到 y结点最短路径上所有节点的值都加上k
  2. if(d[x]<d[y]) swap(x,y);
  3. while(top[x]!=top[y]){//循环,直到这两个点处于同一条链
  4. if(d[top[x]]<d[top[y]]) swap(x,y);//规范
  5. add(1,id[top[x]],id[x],k);
  6. x=f[top[x]];
  7. }
  8. if(d[x]>d[y]) swap(x,y);//深度较浅的一定是序号较小的
  9. add(1,id[x],id[y],k);
  10. }

其实原理就和倍增求LCA差不多。

操作二:

  1. void func2(int x,int y){
  2. int ans=0;
  3. while(top[x]!=top[y]){
  4. if(d[top[x]]<d[top[y]]) swap(x,y);
  5. ans=(ans+ask(1,id[top[x]],id[x]))%mod;
  6. x=f[top[x]];
  7. }
  8. if(d[x]>d[y]) swap(x,y);//道理同上
  9. ans=(ans+ask(1,id[x],id[y]));
  10. cout<<ans%mod<<endl;
  11. }

原理和func1差不多啦~

操作三&&操作四:

这里的处理比较巧妙。

在树剖中,一条链的编号是连续的,因此一棵子树的编号也是连续的。

所以直接用线段树的区间修改和区间查询操作就行了。

Code:

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=1e6+5;
  5. int n,m,root,mod;
  6. int idx=0;
  7. struct node{
  8. int l;
  9. int r;
  10. int sum;
  11. int lazy;
  12. }tree[4*N];
  13. int head[N],ver[2*N],nex[2*N],son[N],size[N],f[N],d[N],top[N],id[N],rk[N];
  14. int input[N];
  15. int cnt=0;
  16. void add_e(int x,int y){
  17. ver[++idx]=y;
  18. nex[idx]=head[x];
  19. head[x]=idx;
  20. }
  21.  
  22. void build(int l,int r,int i){//常规建树
  23. tree[i].l=l;
  24. tree[i].r=r;
  25. if(l==r){
  26. tree[i].sum=input[rk[l]]%mod;
  27. return ;
  28. }
  29. int mid=(l+r)/2;
  30. build(l,mid,2*i);
  31. build(mid+1,r,2*i+1);
  32. tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%mod;
  33. }
  34.  
  35. void push_down(int i){//延迟标记下移
  36. if(tree[i].lazy){
  37. tree[2*i].sum=(tree[2*i].sum+(tree[2*i].r-tree[2*i].l+1)* tree[i].lazy%mod)%mod;
  38. tree[2*i+1].sum=(tree[2*i+1].sum+(tree[2*i+1].r-tree[2*i+1].l+1)* tree[i].lazy%mod)%mod;
  39. tree[2*i].lazy+=tree[i].lazy;
  40. tree[2*i+1].lazy+=tree[i].lazy;
  41. tree[i].lazy=0;
  42. }
  43. }
  44.  
  45. int ask(int i,int L,int R){//区间查询
  46. if(tree[i].l>=L&&tree[i].r<=R) return tree[i].sum;
  47. push_down(i);
  48. int ans=0;
  49. int mid=(tree[i].l+tree[i].r)/2;
  50. if(mid>=L) ans=(ans+ask(2*i,L,R))%mod;
  51. if(mid<R) ans=(ans+ask(2*i+1,L,R))%mod;
  52. return ans%mod;
  53. }
  54.  
  55. void add(int i,int L,int R,int k){//区间修改 ;
  56. if(tree[i].l>=L&&tree[i].r<=R){
  57. tree[i].sum=(tree[i].sum+(tree[i].r-tree[i].l+1)*k%mod)%mod;
  58. tree[i].lazy+=k;
  59. return ;
  60. }
  61. push_down(i);
  62. int mid=(tree[i].l+tree[i].r)/2;
  63. if(L<=mid) add(2*i,L,R,k);
  64. if(mid<R) add(2*i+1,L,R,k);
  65. tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%mod;
  66. }
  67.  
  68. void dfs1(int x,int fa,int depth){//x:当前结点 fa:父结点 depth:当前结点深度
  69. f[x]=fa;//更新父结点
  70. d[x]=depth;//更新深度
  71. size[x]=1;//子树大小初始化:根节点本身
  72. for(int i=head[x];i;i=nex[i]){
  73. int y=ver[i];
  74. if(y==fa) continue ;
  75. dfs1(y,x,depth+1);
  76. if(size[y]>size[son[x]]) son[x]=y;//更新重儿子
  77. size[x]+=size[y];//更新子树大小
  78. }
  79. return ;
  80. }
  81.  
  82. void dfs2(int u,int t){//u为当前结点,t为链段
  83. top[u]=t;
  84. id[u]=++cnt;
  85. rk[cnt]=u;//新的编号
  86. if(!son[u]) return ;
  87. dfs2(son[u],t);//优先遍历重儿子,使一条链上编号连续
  88. for(int i=head[u];i;i=nex[i]){
  89. int y=ver[i];
  90. if(y==son[u]||y==f[u]) continue;
  91. dfs2(y,y);//再建一条链
  92. }
  93. }
  94.  
  95. void func1(int x,int y,int k){//将树从 x到 y结点最短路径上所有节点的值都加上k
  96. if(d[x]<d[y]) swap(x,y);
  97. while(top[x]!=top[y]){//循环,直到这两个点处于同一条链
  98. if(d[top[x]]<d[top[y]]) swap(x,y);//规范
  99. add(1,id[top[x]],id[x],k);
  100. x=f[top[x]];
  101. }
  102. if(d[x]>d[y]) swap(x,y);//深度较浅的一定是序号较小的
  103. add(1,id[x],id[y],k);
  104. }
  105.  
  106. void func2(int x,int y){
  107. int ans=0;
  108. while(top[x]!=top[y]){
  109. if(d[top[x]]<d[top[y]]) swap(x,y);
  110. ans=(ans+ask(1,id[top[x]],id[x]))%mod;
  111. x=f[top[x]];
  112. }
  113. if(d[x]>d[y]) swap(x,y);//道理同上
  114. ans=(ans+ask(1,id[x],id[y]));
  115. cout<<ans%mod<<endl;
  116. }
  117. signed main(){
  118. cin>>n>>m>>root>>mod;
  119. for(int i=1;i<=n;i++){
  120. cin>>input[i];//输入节点初始值
  121. }
  122. for(int i=1;i<=n-1;i++){
  123. int x,y;
  124. cin>>x>>y;
  125. add_e(x,y);
  126. add_e(y,x);//建图
  127. }
  128. dfs1(root,0,1);//第一次dfs求son,depth,f,size
  129. dfs2(root,root);//第二次dfs求id,rk,将树拆成链表
  130. build(1,n,1);//建树
  131. for(int i=1;i<=m;i++){
  132. int type;
  133. cin>>type;
  134. if(type==1){
  135. int x,y,z;
  136. cin>>x>>y>>z;
  137. func1(x,y,z);
  138. }
  139. if(type==2){
  140. int x,y;
  141. cin>>x>>y;
  142. func2(x,y);
  143. }
  144. if(type==3){
  145. int x,z;
  146. cin>>x>>z;
  147. add(1,id[x],id[x]+size[x]-1,z);
  148. }
  149. if(type==4){
  150. int x;
  151. cin>>x;
  152. cout<<ask(1,id[x],id[x]+size[x]-1)%mod<<endl;
  153. }
  154. }
  155. return 0;
  156. }

完结撒花*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。

 

线段树&数链剖分的更多相关文章

  1. G - Game HDU - 5242 (数链剖分)

    题目链接: G - Game HDU - 5242 题目大意:首先是T组测试样例,给出一颗以1节点为根的树,每个节点有各自的价值,有m次从根节点出发向下走到叶子节点的机会,每次会得到所有经过节点的权值 ...

  2. 数链剖分(树的统计Count )

    题目链接:https://cn.vjudge.net/contest/279350#problem/C 具体思路:单点更新,区间查询,查询的时候有两种操作,查询区间最大值和区间和. 注意点:在查询的时 ...

  3. 数链剖分(Aragorn's Story )

    题目链接:https://vjudge.net/contest/279350#problem/A 题目大意:n个点,m条边,然后q次询问,因为在树上,两个点能确定一条直线,我们可以对这条直线上的所有值 ...

  4. POJ 2352 Stars 线段树 数星星

    转载自 http://www.cnblogs.com/fenshen371/archive/2013/07/25/3214927.html 题意:已知n个星星的坐标.每个星星都有一个等级,数值等于坐标 ...

  5. 数链剖分(Tree)

    题目链接:https://cn.vjudge.net/contest/279350#problem/D 题目大意:操作,单点查询,区间取反,询问区间最大值. AC代码: #include<ios ...

  6. 数链剖分(Housewife Wind )

     题目链接:https://vjudge.net/contest/279350#problem/B 题目大意:给你n,q,s.n指的是有n个点,q代表有q次询问,s代表的是起点.然后接下来会有n-1条 ...

  7. UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html 题目传送门 - UOJ#30 题意 uoj写的很简洁.清晰,这里就不抄一遍了. 题解 首先建 ...

  8. POJ 2763 Housewife Wind 【树链剖分】+【线段树】

    <题目链接> 题目大意: 给定一棵无向树,这棵树的有边权,这棵树的边的序号完全由输入边的序号决定.给你一个人的起点,进行两次操作: 一:该人从起点走到指定点,问你这段路径的边权总和是多少. ...

  9. BZOJ1758[Wc2010]重建计划——分数规划+长链剖分+线段树+二分答案+树形DP

    题目描述 输入 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai, ...

随机推荐

  1. 从微信小程序到鸿蒙js开发【05】——tabs组件&每日新闻

    目录: 1.tabs, tab-bar, tab-content 2.tabs的事件处理 3.tabs实现的每日新闻 1.tabs, tab-bar, tab-content 上章说到,鸿蒙的list ...

  2. SOLID:面向对象设计的五个基本原则

    在程序设计领域,SOLID 是由罗伯特·C·马丁在 21 世纪早期引入的记忆术首字母缩略字,指代了面向对象编程和面向对象设计的五个基本原则.当这些原则被一起应用时,它们使得一个程序员开发一个容易进行软 ...

  3. springBoot之 spring-boot-starter-parent 引入详解

    springBoot中引入 <parent> <groupId>org.springframework.boot</groupId> <artifactId& ...

  4. leetcode常见问题

    开学了 开始每日刷leetcode了  ,开一个新分类记录做题过程和心得. 1.出现本地调试无问题但提交后报错时,很有可能是全局变量导致的,解决办法 (1).尽量写成局部变量,函数尽量传参进入. (2 ...

  5. 1006 How many?

    Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 36   Accepted: 2 Description 有一天,小Q给了小J ...

  6. 牛客小白月赛30 B.最好的宝石 (线段树)

    题意:RT. 题解:很明显的线段树维护区间最大值操作,但是我们同时还要维护最大值的个数,我们在build或者modify操作完子树然后push_up的时候,我们先从两个儿子取max更新父节点的最大值, ...

  7. Codeforces Round #643 (Div. 2) E. Restorer Distance (贪心,三分)

    题意:给你\(n\)个数,每次可以使某个数++,--,或使某个数--另一个++,分别消耗\(a,r,m\).求使所有数相同最少的消耗. 题解:因为答案不是单调的,所以不能二分,但不难发现,答案只有一个 ...

  8. Codeforces Round #666 (Div. 2) C. Multiples of Length (贪心)

    题意:给你一个由\(0,1,?\)组成的字符串,你可以将\(?\)任意改成\(0\)或\(1\),问你操作后能否使得该字符串的任意长度为\(k\)的区间中的\(0\)和$1的个数相等. 题解:我们首先 ...

  9. 开源RPA软件试用

      优点 缺点 其它 Robot Framework 可视化界面 运行环境搭建复杂,依赖较多 操作复杂 倾向于自动化测试 TagUI 浏览器支持好 官方文档详细 命令行操作 非浏览器程序支持一般   ...

  10. [Golang]-4 错误处理、Panic、Defer

    目录 错误和异常 案例 Panic Defer 使用 defer+recover 来处理错误 参考文章: Go 语言使用一个独立的·明确的返回值来传递错误信息的.这与使用异常的 Java 和 Ruby ...