依然延续第一篇读书笔记,这一篇是基于《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。(在需要时才进行更新是一个很好的算法优化)

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

 /*Tree*/
struct Node{
int l, r; //左右端点坐标
int value; //值
int add; //子树各结点应add的值
}tree[MAXN];

  

  搭建

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

  具体如下:

 /*从x结点开始扩展线段树*/
void build(int x, int l, int r)
{
tree[x].l = l;
tree[x].r = r;
if (l == r){
tree[x].value = source[l];
return;
}
int mid = (l + r) / ;
build(x * , l, mid);
build(x * + , mid + , r);
tree[x].value = tree[ * x].value + tree[ * x + ].value;
tree[x].add = ;
}

  更新

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

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

 /*更新-在[l,r]线段上加上m*/
void update(int x, int l, int r, int m)
{
// update
tree[x].value += m*(r - l + );
// Hit!
if (tree[x].l == l && tree[x].r == r){
tree[x].add += m;
return;
}
// add - Transfer
if (tree[x].add){
tree[ * x].add += tree[x].add;
tree[ * x].value += tree[x].add*(tree[ * x].r - tree[ * x].l + );
tree[ * x + ].add += tree[x].add;
tree[ * x + ].value += tree[x].add*(tree[ * x + ].r - tree[ * x + ].l + );
tree[x].add = ;
}
// continue - Search
int mid = (tree[x].l + tree[x].r)/;
if (r <= mid) //[l,r]在mid右侧
update( * x, l, r, m);
else if (l >= mid) //[l,r]在mid左侧
update( * x + , l, r, m);
else{ //[l,r]横跨mid
update( * x, l, mid, m);
update( * x + , mid + , r, m);
}
}

  查询

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

 //最终查询值
int ans = ;
/*查询*/
void query(int x, int l, int r)
{
// Hit!
if (tree[x].l == l && tree[x].r == r)
{
ans += tree[x].value;
return;
}
// add - Transfer
if (tree[x].add){
tree[ * x].add += tree[x].add;
tree[ * x].value += tree[x].add*(tree[ * x].r - tree[ * x].l + );
tree[ * x + ].add += tree[x].add;
tree[ * x + ].value += tree[x].add*(tree[ * x + ].r - tree[ * x + ].l + );
tree[x].add = ;
}
// continue - Search
int mid = (tree[x].l + tree[x].r)/;
if (r <= mid) //[l,r]在mid左侧
query( * x, l, r);
else if (l >= mid) //[l,r]在mid右侧
query( * x + , l, r);
else{ //[l,r]横跨mid
query( * x, l, mid);
query( * x + , mid + , r);
}
}

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

    最少需要空间为2*MAX,最多需要空间为4*MAX


  在POJ上有一个裸线段树例题---POJ3468

  题目大意就是给一个区间上的sum进行两个操作-1,查询,2,区间上每个点完成一次加法。

 //线段处理-线段树
//在一个区间内处理数据的加减和查询-裸线段树
//Memory:6732K Time:1579Ms
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std; #define MAX 100005 int n, q; //n:原数据量 q:查询量
int s[MAX]; //source date
__int64 ans; //查询结果 /*interval_tree*/
struct Node{
int l, r;
__int64 value;
__int64 add;
}tr[*MAX]; //线段树最少需要2*MAX,最多需要4*MAX /*搭建interval-tree*/
void build(int x,int l,int r)
{
tr[x].l = l;
tr[x].r = r;
if (tr[x].l == tr[x].r){ //规模缩减到单个数据
tr[x].value = s[l];
return;
}
int mid = (l + r) / ;
build( * x, l, mid);
build( * x + , mid + , r);
tr[x].value = tr[ * x].value + tr[ * x + ].value;//该结点value由子树结点决定
tr[x].add = ; //Init
} /*更新-从x向下扩展每个结点+m*/
void update(int x,int l,int r,int m)
{
// update
tr[x].value += m*(r - l + );
// Hit
if (tr[x].l == l && tr[x].r == r){
tr[x].add += m;
return;
}
// add - transfer
if (tr[x].add){
tr[ * x].add += tr[x].add;
tr[ * x + ].add += tr[x].add;
tr[ * x].value += tr[x].add*(tr[ * x].r - tr[ * x].l + );
tr[ * x + ].value += tr[x].add*(tr[ * x + ].r - tr[ * x + ].l + );
tr[x].add = ;
}
// Search
int mid = (tr[x].r + tr[x].l) / ; //该段中点
if (r <= mid)
update( * x, l, r, m);
else if (l > mid)
update( * x + , l, r, m);
else{
update( * x, l, mid, m);
update( * x + , mid + , r, m);
}
} /*查询-interval-date*/
void query(int x,int l,int r)
{
// Hit
if (tr[x].l == l && tr[x].r == r){
ans += tr[x].value;
return;
}
// add - transfer
if (tr[x].add){
tr[ * x].add += tr[x].add;
tr[ * x + ].add += tr[x].add;
tr[ * x].value += tr[x].add*(tr[ * x].r - tr[ * x].l + );
tr[ * x + ].value += tr[x].add*(tr[ * x + ].r - tr[ * x + ].l + );
tr[x].add = ;
}
// Search
int mid = (tr[x].r + tr[x].l) / ; //该段中点
if (r <= mid)
query( * x, l, r);
else if (l > mid)
query( * x + , l, r);
else{
query( * x, l, mid);
query( * x + , mid + , r);
}
} int main()
{
scanf("%d%d", &n, &q);
for (int i = ; i <= n; i++)
scanf("%d", &s[i]);
build(, , n); //Creat_interval tree
while (q--)
{
char ch; //command
int low, high, dig;
scanf("\n%c", &ch);
if (ch == 'C'){
scanf("%d%d%d", &low, &high, &dig);
update(, low, high, dig);
}
else if (ch == 'Q'){
ans = ;
scanf("%d%d", &low, &high);
query(, low, high);
printf("%I64d\n", ans);
}
} return ;
}

小墨- -原创


算法手记 之 数据结构(线段树详解)(POJ 3468)的更多相关文章

  1. 线段树详解 (原理,实现与应用)(转载自:http://blog.csdn.net/zearot/article/details/48299459)

    原文地址:http://blog.csdn.net/zearot/article/details/48299459(如有侵权,请联系博主,立即删除.) 线段树详解    By 岩之痕 目录: 一:综述 ...

  2. zkw线段树详解

    转载自:http://blog.csdn.net/qq_18455665/article/details/50989113 前言 首先说说出处: 清华大学 张昆玮(zkw) - ppt <统计的 ...

  3. hdu 1166 敌兵布阵(线段树详解)

    Problem Description C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任 ...

  4. HDU1166 敌兵布阵 线段树详解

    题解: 更新是线段树的单点更新,简单一点. 有50000个阵营,40000查询,用普通数组肯定超时.区间求和和区间查询问题用线段树最好不过了. 先说说什么是线段树. 区间[1,10]用树的方法存起来, ...

  5. 数据结构图文解析之:AVL树详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  6. trie字典树详解及应用

    原文链接    http://www.cnblogs.com/freewater/archive/2012/09/11/2680480.html Trie树详解及其应用   一.知识简介        ...

  7. Linux dts 设备树详解(一) 基础知识

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...

  8. Linux DTS(Device Tree Source)设备树详解之二(dts匹配及发挥作用的流程篇)【转】

    转自:https://blog.csdn.net/radianceblau/article/details/74722395 版权声明:本文为博主原创文章,未经博主允许不得转载.如本文对您有帮助,欢迎 ...

  9. [转]EM算法(Expectation Maximization Algorithm)详解

    https://blog.csdn.net/zhihua_oba/article/details/73776553 EM算法(Expectation Maximization Algorithm)详解 ...

随机推荐

  1. 基本linux命令

    1.mkdir mkdir 创建目录   mkdir -p 循环创建目录 2.cd     切换目录 3.pwd   查看当前路径 4.mkdir 删除一个空的目录 5.cp 复制文件/目录  -r用 ...

  2. .NET逻辑分层架构总结

    一.基础知识准备: 1.层的原则: (1)每一层以接口方式供上层调用. (2)上层只能调用下层. (3)依赖分为松散交互和严格交互两种. 2.业务逻辑分类: (1)应用逻辑. (2)领域逻辑. 3.采 ...

  3. 利用session防止用户未经登录而直接访问

    在编写项目的时候,突然想如果按常理出牌,不首先进入登录界面而直接访问网页内容,可不可以呢?如此一来便尝试了一下,整的可以直接进入管理员页面,获取完全的管理权限.于是在网上查看了一下解决方案,学习了一下 ...

  4. PHP 如何显示大数字,防止显示为 科学计数法 形式

    PHP 数字超过一定长度时,会自动转换为 科学计数法 的形式,如 1.2345678912346E+16: 如何 避免转换,让它原样展示呢? 不过,可以用PHP函数 number_format() 来 ...

  5. jQuery的$.ajax示例

    $.ajax({ url: 'index.php?module=products&submod=product_experience_manage&method=ajaxGetSele ...

  6. InnoDB为什么要使用auto_Increment

    在Mysql表设计中,通常会使用一个与业务无关的自增列做为主键.这是因为Mysql默认使用B-Tree索引,你可以简单理解为"排好序的快速查找结构".如下是一个B-Tree的结构图 ...

  7. 采用Atlas+Keepalived实现MySQL读写分离、读负载均衡【转载】

      文章 原始出处 :http://sofar.blog.51cto.com/353572/1601552 ============================================== ...

  8. grunt 使用

    grunt 服务端, grunt-cli 客户端 1.grunt 插件安装: package.json, 存放grunt所需插件 { "name": "demo" ...

  9. struts2-(1)使用Filter作为控制器

    1.使用filter作为控制器 (1)创建类,实现javax.servlet.Filter package com.controller.filter; import java.io.IOExcept ...

  10. C++中的单例模式(转)

    单例模式也称为单件模式.单子模式,可能是使用最广泛的设计模式.其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享.有很多地方需要这样的功能模块,如系统的日志输出,G ...