《ACM/ICPC 算法训练教程》读书笔记 之 数据结构(线段树详解)

依然延续第一篇读书笔记,这一篇是基于《ACM/ICPC 算法训练教程》上关于线段树的讲解的总结和修改(这本书在线段树这里Error非常多),但是总体来说这本书关于具体算法的讲解和案例都是不错的。



线段树简介  这是一种二叉搜索树,类似于区间树,是一种描述线段的树形数据结构,也是ACMer必学的一种数据结构,主要用于查询对一段数据的处理和存储查询,对时间度的优化也是较为明显的,优化后的时间复杂为O(logN)。此外,线段树还可以拓展为点树ZWK线段树等等,与此类似的还有树状数组等等。

  例如:要将数组s[]从[i,j]段上的元素均加上b,那么我们通常需要遍历每个元素(s[i],s[i+1]...s[j])并+b,此时使用的操作数为(j-i+1)次,但如果我们在某些情况下只关心[i,j]段内的总和呢,此时我们只需在[i,j]段内总和sum的基础上+b*(j-i+1)就行了,这样的操作数只需要一次。

  再者,若想知道[i,j]段内的和,直接输出此前存储的总和sum,这样比每次查询时都要遍历(j-i+1)个元素要好得多,因此参照树形结构可以引入一种表示一条线段上数据的结构。

  用数组模拟可以直观表述线段树如右图:



  具体实现和相应改进Code:

  定义

  每个结点的定义可以暂时如下:

struct Node{
    int l, r;
    int value;
}tree[MAXN];

  上面是一种简单直接的表示,但是对于需要经常更新数值的线段树来说,这种定义让线段树时间优化变得优势全无。

  因为如果对每一个[i,j]内的线段上每一个元素+b时,作为一段数据,我们可以+b*(j-i+1),但这一段的子树上的数据又该如何表示呢,难道一直遍历下去直到所有子结点遍历完并更新其中的数据嘛,这明显是个很愚蠢的做法,这样做会使得线段树的效率下降不少。

  我们在结点的定义上引入一个增量add(初始为0),使得每次更新数据时,在该结点及其子树全部更新数据后,再在该结点的增量add上+b,这样在每次查询或更新到它的子结点时,必然会遍历到该结点,此时查询该结点的add是否为0,如果不为0,则将add的值向下传递,更新子树结点上的value。(在需要时才进行更新是一个很好的算法优化)

  因此我们可以改进上面关于结点的定义,最终定义如下:

1 /*Tree*/
2 struct Node{
3     int l, r;
4     int value;
5     int add;
6 }tree[MAXN];

  

  搭建

  那么我们该如何搭建一个线段树呢,我们利用树形结构的思想,不断得二分得到左儿子和右儿子。原结点的value就靠左右儿子的value相加得到。

  具体如下:

 1 /*从x结点开始扩展线段树*/
 2 void build(int x, int l, int r)
 3 {
 4     tree[x].l = l;
 5     tree[x].r = r;
 6     if (l == r){
 7         tree[x].value = source[l];
 8         return;
 9     }
10     int mid = (l + r) / 2;
11     build(x * 2, l, mid);
12     build(x * 2 + 1, mid + 1, r);
13     tree[x].value = tree[2 * x].value + tree[2 * x + 1].value;
14     tree[x].add = 0;
15 }

  更新

  此处开始对书上的Code做了修改和改进。

  那么为了进行一段数据上数据的更新,我们在上面已经引入了add增量表示,具体做法如下:

 1 /*更新-在[l,r]线段上加上m*/
 2 void update(int x, int l, int r, int m)
 3 {
 4     // update
 5     tree[x].value += m*(r - l + 1);
 6     // Hit!
 7     if (tree[x].l == l && tree[x].r == r){
 8         tree[x].add += m;
 9         return;
10     }
11     // add - Transfer
12     if (tree[x].add){
13         tree[2 * x].add += tree[x].add;
14         tree[2 * x].value += tree[x].add*(tree[2 * x].r - tree[2 * x].l + 1);
15         tree[2 * x + 1].add += tree[x].add;
16         tree[2 * x + 1].value += tree[x].add*(tree[2 * x + 1].r - tree[2 * x + 1].l + 1);
17         tree[x].add = 0;
18     }
19     // continue - Search
20     int mid = (tree[x].l + tree[x].r)/2;
21     if (r <= mid)        //[l,r]在mid右侧
22         update(2 * x, l, r, m);
23     else if (l >= mid)    //[l,r]在mid左侧
24         update(2 * x + 1, l, r, m);
25     else{                //[l,r]横跨mid
26         update(2 * x, l, mid, m);
27         update(2 * x + 1, mid + 1, r, m);
28     }
29 }

  查询

  也就是查询某段上的数据value

 1 //最终查询值
 2 int ans = 0;
 3 /*查询*/
 4 void query(int x, int l, int r)
 5 {
 6     // Hit!
 7     if (tree[x].l == l && tree[x].r == r)
 8     {
 9         ans += tree[x].value;
10         return;
11     }
12     // add - Transfer
13     if (tree[x].add){
14         tree[2 * x].add += tree[x].add;
15         tree[2 * x].value += tree[x].add*(tree[2 * x].r - tree[2 * x].l + 1);
16         tree[2 * x + 1].add += tree[x].add;
17         tree[2 * x + 1].value += tree[x].add*(tree[2 * x + 1].r - tree[2 * x + 1].l + 1);
18         tree[x].add = 0;
19     }
20     // continue - Search
21     int mid = (tree[x].l + tree[x].r)/2;
22     if (r <= mid)        //[l,r]在mid左侧
23         query(2 * x, l, r);
24     else if (l >= mid)    //[l,r]在mid右侧
25         query(2 * x + 1, l, r);
26     else{                //[l,r]横跨mid
27         query(2 * x, l, mid);
28         query(2 * x + 1, mid + 1, r);
29     }
30 }

  另外对于一个源数组source[MAX],线段树往往所需的空间要稍大一点,大约为4*MAX.



《ACM/ICPC 算法训练教程》读书笔记 之 数据结构(线段树详解)

时间: 2024-10-04 17:06:18

《ACM/ICPC 算法训练教程》读书笔记 之 数据结构(线段树详解)的相关文章

《ACM/ICPC 算法训练教程》读书笔记一之数据结构(堆)

书籍简评:<ACM/ICPC 算法训练教程>这本书是余立功主编的,代码来自南京理工大学ACM集训队代码库,所以小编看过之后发现确实很实用,适合集训的时候刷题啊~~,当时是听了集训队final的意见买的,感觉还是不错滴. 相对于其他ACM书籍来说,当然如书名所言,这是一本算法训练书,有着大量的算法实战题目和代码,尽管小编还是发现了些许错误= =,有部分注释的语序习惯也有点不太合我的胃口.实战题目较多是比较水的题,但也正因此才能帮助不少新手入门,个人认为还是一本不错的算法书,当然自学还是需要下不少

读书笔记 之 数据结构(并查集详解)(POJ1703)

<ACM/ICPC算法训练教程>读书笔记-这一次补上并查集的部分.将对并查集的思想进行详细阐述,并附上本人AC掉POJ1703的Code. 在一些有N个元素的集合应用问题中,通常会将每个元素构成单元素集合,然后按照一定顺序将同属一组的集合合并,期间要反复查找每一个元素在哪个集合中.这类问题往往看似简单,但是数据量很大,因此容易造成TLE或MLE,也就是空间度和时间度极其复杂.因此在这里,我们引入一种抽象的特殊数据结构——并查集. 并查集:类似一个族谱,每个结点均有一个father[x]来表示x

算法导论读书笔记(16)

算法导论读书笔记(16) 目录 动态顺序统计 检索具有给定排序的元素 确定一个元素的秩 区间树 步骤1:基础数据结构 步骤2:附加信息 步骤3:维护信息 步骤4:设计新操作 动态顺序统计 之前介绍过 顺序统计 的概念.在一个无序的集合中,任意的顺序统计量都可以在 O ( n )时间内找到.而这里我们将介绍如何在 O ( lg n )时间内确定任意的顺序统计量. 下图显示的是一种支持快速顺序统计量操作的数据结构.一棵 顺序统计树 T 通过在红黑树的每个结点中存入附加信息而成.在一个结点 x 内,增

程序语言的奥妙:算法解读 &mdash;&mdash;读书笔记

算法(Algorithm) 是利用计算机解决问题的处理步骤. 算法是古老的智慧.如<孙子兵法>,是打胜仗的算法. 算法是古老智慧的结晶,是程序的范本. 学习算法才能编写出高质量的程序. 懂得了算法,游戏水平会更高. 比如下棋,如果懂得棋谱,就不需要每次考虑"寻找最好的一步棋",按照棋谱 就可以走出最好的几步棋.棋谱是先人们智慧的结果,因此掌握多种棋谱的人更 容易在对弈中获得胜利. 算法的学习类似学习游戏攻略. 算法是编写好程序的"棋谱". 算法必须满足&

算法导论读书笔记之钢条切割问题

算法导论读书笔记之钢条切割问题 巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo) 给定一段长度为n英寸的钢条和一个价格表 pi (i=1,2, -,n),求切割钢条的方案,使得销售收益rn最大.注意,如果长度为n英寸的钢条价格pn足够大,最优解可能就是完全不需要切割. 若钢条的长度为i,则钢条的价格为Pi,如何对给定长度的钢条进行切割能得到最大收益? 长度i   1   2    3   4     5      6     7     8  

算法导论读书笔记(15) - 红黑树的具体实现

算法导论读书笔记(15) - 红黑树的具体实现 目录 红黑树的简单Java实现 红黑树的简单Java实现 /** * 红黑树 * * 部分代码参考自TreeMap源码 */ public class RedBlackTree<T> { protected TreeNode<T> root = null; private final Comparator<? super T> comparator; private int size = 0; private static

ACM/ICPC算法训练 之 数学很重要-浅谈“排列计数” (DP题-POJ1037)

这一题是最近在看Coursera的<算法与设计>的公开课时看到的一道较难的DP例题,之所以写下来,一方面是因为DP的状态我想了很久才想明白,所以借此记录,另一方面是看到这一题有运用到 排列计数 的方法,虽然排列计数的思路简单,但却是算法中一个数学优化的点睛之笔. Poj1037  A decorative fence 题意:有K组数据(1~100),每组数据给出总木棒数N(1~20)和一个排列数C(64位整型范围内),N个木棒长度各异,按照以下条件排列,并将所有可能结果进行字典序排序 1.每一

算法导论读书笔记(17)

算法导论读书笔记(17) 目录 动态规划概述 钢条切割 自顶向下的递归实现 使用动态规划解决钢条切割问题 子问题图 重构解 钢条切割问题的简单Java实现 动态规划概述 和分治法一样, 动态规划 (dynamic programming)是通过组合子问题的解而解决整个问题的.分治法是将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解.与此不同,动态规划适用于子问题并不独立的情况,即各子问题包含公共的子子问题.在这种情况下,分治法会重复地求解公共的子子问题.而动态

算法导论读书笔记(14) - 二叉查找树的具体实现

算法导论读书笔记(14) - 二叉查找树的具体实现 目录 二叉查找树的简单Java实现 二叉查找树的简单Java实现 /** * 二叉查找树 * 部分代码参考自TreeMap的源码 */ public class BinarySearchTree<T> { protected TreeNode<T> root = null; private final Comparator<? super T> comparator; private int size = 0; pub