0 写在前面

怎么说呢,其实从入坑线段树一来,经历过两个阶段,第一个阶段是初学阶段,那个时候看网上的一些教学博文和模板入门了线段树,

然后挑选了一个线段树模板作为自己的模板,经过了一点自己的修改,然后就已知用着,其实对线段树理解不深,属于就会套个模板的状态,期间有人问我线段树的问题,我其实也半知不解的,

后来,刷了几道DFS序+线段树的题目,那个时候多多少少有所长进,再次回过头来重新看线段树的代码,理解有所加深,算是勉强理清了线段树这个东西,

再到现在,前不久刚把splay搞完,对平衡二叉搜索树有了更加深的理解,而且线段树相比splay,还是比较简单的,所以终于下定决心,好好整理一下,把线段树这一块理清晰理透彻。


1 线段树模板

2.0 单点修改,区间查询线段树

一开始我没有把这种线段树考虑进来……因为比较简单,有lazy的线段树才是好线段树!

模板可以参见:计蒜客 30996 - Lpl and Energy-saving Lamps

2.1 区间修改,区间求和线段树模板

先是最基础的区间修改,区间求和线段树模板:

#include<bits/stdc++.h>
using namespace std;
const int maxn=+; int n,a[maxn]; /********************************* Segment Tree - st *********************************/
struct Node{
int l,r;
int val,lazy;
void update(int x)
{
val+=(r-l+)*x;
lazy+=x;
}
}node[*maxn];
void pushdown(int root)
{
if(node[root].lazy)
{
node[root*].update(node[root].lazy);
node[root*+].update(node[root].lazy);
node[root].lazy=;
}
}
void pushup(int root)
{
node[root].val=node[root*].val+node[root*+].val;
}
void build(int root,int l,int r) //对区间[l,r]建树
{
node[root].l=l; node[root].r=r;
node[root].val=; node[root].lazy=;
if(l==r) node[root].val=a[l];
else
{
int mid=l+(r-l)/;
build(root*,l,mid);
build(root*+,mid+,r);
pushup(root);
}
}
void update(int root,int st,int ed,int val) //区间[st,ed]全部加上val
{
if(st>node[root].r || ed<node[root].l) return;
if(st<=node[root].l && node[root].r<=ed) node[root].update(val);
else
{
pushdown(root);
update(root*,st,ed,val);
update(root*+,st,ed,val);
pushup(root);
}
}
int query(int root,int st,int ed) //查询区间[st,ed]的和
{
if(st>node[root].r || ed<node[root].l) return ;
if(st<=node[root].l && node[root].r<=ed) return node[root].val;
else
{
pushdown(root);
int ls=query(root*,st,ed);
int rs=query(root*+,st,ed);
pushup(root);
return ls+rs;
}
}
/********************************* Segment Tree - ed *********************************/ int main()
{
int T;
cin>>T;
for(int kase=;kase<=T;kase++)
{
scanf("%d",&n);
for(int i=;i<=n;i++) scanf("%d",&a[i]);
build(,,n); printf("Case %d:\n",kase);
char op[];
while()
{
scanf("%s",op);
if(op[]=='E') break;
if(op[]=='A')
{
int p,x; scanf("%d%d",&p,&x);
update(,p,p,x);
}
if(op[]=='S')
{
int p,x; scanf("%d%d",&p,&x);
update(,p,p,-x);
}
if(op[]=='Q')
{
int l,r; scanf("%d%d",&l,&r);
printf("%d\n",query(,l,r));
}
}
}
}

2.2 原理要点总结

线段树的原理其实很简单,总结来说有下面几个要点:

  1. 把二叉树的节点按从上到下、从左到右存在一个数组里的话,对于每个节点x,它与左右儿子的关系是:左儿子 ls = 2*x,右儿子 rs = 2*x + 1;
  2. 线段树每个节点存储的值是由左右儿子节点的值O(1)得到的;
  3. 每一次区间更新,只对 属于该区间的 又是最靠上层的 的节点进行操作,这个操作有两部分(Node结构体中的成员函数update):①修改本节点的值,②给本节点打上lazy标记;
  4. lazy标记:某个节点如果打着lazy标记,表明它的儿子们还没有更新;
  5. 每访问到一个节点(不管是更新的访问还是查询的访问),如果要继续深入到其儿子们,显然就要先把lazy标记push下去,一旦push下去就要记得再push上来;

2.3 区间修改,区间最值线段树模板

区间修改,区间最小值线段树模板:

#include<bits/stdc++.h>
using namespace std;
const int maxn=+;
const int INF=0x3f3f3f3f; int n,a[maxn]; /********************************* Segment Tree - st *********************************/
struct Node{
int l,r;
int val,lazy;
void update(int x)
{
val+=x;
lazy+=x;
}
}node[*maxn];
void pushdown(int root)
{
if(node[root].lazy)
{
node[root*].update(node[root].lazy);
node[root*+].update(node[root].lazy);
node[root].lazy=;
}
}
void pushup(int root)
{
node[root].val=min(node[root*].val,node[root*+].val);
}
void build(int root,int l,int r) //对区间[l,r]建树
{
node[root].l=l; node[root].r=r;
node[root].val=; node[root].lazy=;
if(l==r) node[root].val=a[l];
else
{
int mid=l+(r-l)/;
build(root*,l,mid);
build(root*+,mid+,r);
pushup(root);
}
}
void update(int root,int st,int ed,int val) //区间[st,ed]全部加上val
{
if(st>node[root].r || ed<node[root].l) return;
if(st<=node[root].l && node[root].r<=ed) node[root].update(val);
else
{
pushdown(root);
update(root*,st,ed,val);
update(root*+,st,ed,val);
pushup(root);
}
}
int query(int root,int st,int ed) //查询区间[st,ed]的最小值
{
if(st>node[root].r || ed<node[root].l) return INF;
if(st<=node[root].l && node[root].r<=ed) return node[root].val;
else
{
pushdown(root);
int ls=query(root*,st,ed);
int rs=query(root*+,st,ed);
pushup(root);
return min(ls,rs);
}
}
/********************************* Segment Tree - ed *********************************/ int main()
{
memset(a,,sizeof(a));
n=; build(,,n); update(,,,);
for(int i=;i<=n;i++) cout<<query(,i,i)<<" "; cout<<endl;
cout<<query(,,n)<<endl; update(,,,-);
for(int i=;i<=n;i++) cout<<query(,i,i)<<" "; cout<<endl;
cout<<query(,,n)<<endl;
}

区间修改,区间最大值线段树模板:

#include<bits/stdc++.h>
using namespace std;
const int maxn=+;
const int INF=0x3f3f3f3f; int n,a[maxn]; /********************************* Segment Tree - st *********************************/
struct Node{
int l,r;
int val,lazy;
void update(int x)
{
val+=x;
lazy+=x;
}
}node[*maxn];
void pushdown(int root)
{
if(node[root].lazy)
{
node[root*].update(node[root].lazy);
node[root*+].update(node[root].lazy);
node[root].lazy=;
}
}
void pushup(int root)
{
node[root].val=max(node[root*].val,node[root*+].val);
}
void build(int root,int l,int r) //对区间[l,r]建树
{
node[root].l=l; node[root].r=r;
node[root].val=; node[root].lazy=;
if(l==r) node[root].val=a[l];
else
{
int mid=l+(r-l)/;
build(root*,l,mid);
build(root*+,mid+,r);
pushup(root);
}
}
void update(int root,int st,int ed,int val) //区间[st,ed]全部加上val
{
if(st>node[root].r || ed<node[root].l) return;
if(st<=node[root].l && node[root].r<=ed) node[root].update(val);
else
{
pushdown(root);
update(root*,st,ed,val);
update(root*+,st,ed,val);
pushup(root);
}
}
int query(int root,int st,int ed) //查询区间[st,ed]的最大值
{
if(st>node[root].r || ed<node[root].l) return -INF;
if(st<=node[root].l && node[root].r<=ed) return node[root].val;
else
{
pushdown(root);
int ls=query(root*,st,ed);
int rs=query(root*+,st,ed);
pushup(root);
return max(ls,rs);
}
}
/********************************* Segment Tree - ed *********************************/ int main()
{
memset(a,,sizeof(a));
n=; build(,,n); update(,,,);
for(int i=;i<=n;i++) cout<<query(,i,i)<<" "; cout<<endl;
cout<<query(,,n)<<endl; update(,,,-);
for(int i=;i<=n;i++) cout<<query(,i,i)<<" "; cout<<endl;
cout<<query(,,)<<endl;
cout<<query(,,n)<<endl;
}

2 线段树的变化与应用

2.1 线段树+DFS序

DFS序:

首先对一棵树进行先序遍历,产生一个序列,用一个数组 in[1~n] 存储每个节点在序列里的位置,显然树根是第一个,所以 in[root] = 1;

同时,由于DFS有回溯存在,所以访问完一个节点的所有子节点(直接的或者间接的),会回到当前节点 x,假设回到当前节点前最后一个访问的节点是 y,我们令 out[x] = in[y];

简单的来说,一棵树进行先序遍历产生一个序列,一个节点 x 其统领的整棵子树在序列上会是一整段区间,而 in[x] 和 out[x] 就是该区间的左右端点。

而线段树配合DFS序,用处就是将“子树修改,子树查询”变成“区间修改,区间查询”,具体请看下面两篇博文:

HDU 5692 - Snacks:http://www.cnblogs.com/dilthey/p/8988368.html

CodeForces 838B - Diverging Directions:http://www.cnblogs.com/dilthey/p/9005129.html

2.2 加乘线段树

顾名思义,就是可以同时完成如下操作的线段树:

  1. 某区间所有数全部加上某个数;
  2. 某区间所有数全部乘上某个数;
  3. 查询某区间所有数之和;

具体代码直接看题目:Luogu 3373

同时还有进阶版:HDU 4578

2.3 线段树+扫描线

求多个矩形的面积并:HDU 1542

求长方体3次及以上体积交:HDU 3642

2.4 线段树优化Dijkstra算法

主要体现了线段树能够适用的场合之广泛。

详见:单源最短路进阶 - “旧王已死,新王当立!” —— 线段树优化Dijkstra算法

线段树(Segment Tree)总结的更多相关文章

  1. 『线段树 Segment Tree』

    更新了基础部分 更新了\(lazytag\)标记的讲解 线段树 Segment Tree 今天来讲一下经典的线段树. 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间 ...

  2. 线段树(Segment Tree)(转)

    原文链接:线段树(Segment Tree) 1.概述 线段树,也叫区间树,是一个完全二叉树,它在各个节点保存一条线段(即“子数组”),因而常用于解决数列维护问题,基本能保证每个操作的复杂度为O(lg ...

  3. BZOJ.4695.最假女选手(线段树 Segment tree Beats!)

    题目链接 区间取\(\max,\ \min\)并维护区间和是普通线段树无法处理的. 对于操作二,维护区间最小值\(mn\).最小值个数\(t\).严格次小值\(se\). 当\(mn\geq x\)时 ...

  4. 【数据结构系列】线段树(Segment Tree)

    一.线段树的定义 线段树,又名区间树,是一种二叉搜索树. 那么问题来了,啥是二叉搜索树呢? 对于一棵二叉树,若满足: ①它的左子树不空,则左子树上所有结点的值均小于它的根结点的值 ②若它的右子树不空, ...

  5. 线段树(segment tree)

    线段树在一些acm题目中经常见到,这种数据结构主要应用在计算几何和地理信息系统中.下图就为一个线段树: (PS:可能你见过线段树的不同表示方式,但是都大同小异,根据自己的需要来建就行.) 1.线段树基 ...

  6. 浅谈线段树 Segment Tree

    众所周知,线段树是algo中很重要的一项! 一.简介 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 使用线段树可以快速的查找某一个节点在 ...

  7. 线段树 Interval Tree

    一.线段树 线段树既是线段也是树,并且是一棵二叉树,每个结点是一条线段,每条线段的左右儿子线段分别是该线段的左半和右半区间,递归定义之后就是一棵线段树. 例题:给定N条线段,{[2, 5], [4, ...

  8. 线段树(I tree)

    Codeforces Round #254 (Div. 2)E题这题说的是给了一个一段连续的区间每个区间有一种颜色然后一个彩笔从L画到R每个区间的颜色都发生了 改变然后 在L和R这部分区间里所用的颜色 ...

  9. segment树(线段树)

    线段树(segment tree)是一种Binary Search Tree或者叫做ordered binary tree.对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+ ...

  10. RMQ问题(线段树+ST算法)

    转载自:http://kmplayer.iteye.com/blog/575725 RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ ...

随机推荐

  1. MySQL------如何关闭打开MySQL

    1.win+R打开运行窗口,输入services.msc 2.在其中查看mysql的服务名,我的是MySQL57 3.以管理员身份打开cmd 停止: 输入net stop MySQL57 启动: 输入 ...

  2. 一个java程序员的年终总结

    年底了,该给自己写点总结了! 从毕业到现在已经快4年啦,一直在Java的WEB开发行业混迹.我不是牛人,但是自我感觉还算是个合格的程序员,有必要写下自己将近4年来的经历,给自我以提示,给刚入行的朋友提 ...

  3. [AX]AX2012 R2 HR Jobs, Positions, Department和Workers

    部门.作业(Job的官方翻译)和位置(Position的官方翻译)是AX人力资源管理的基本组织元素,Job和Position在AX有的地方又称作工作和职位,其实这个翻译更为恰当. Job定义的是一个工 ...

  4. GC--垃圾收集器

    把周末的文章放在现在才来写,是自己太忙了?还是堕落了? 好吧直接进入主题吧,简单干脆的理解会让自己记忆深刻: 首先说明:GC垃圾收集器关注两件事情: 第一件:查找所有存活对象. 第二件:抛弃死对象(不 ...

  5. cygwin设置NDK环境变量ANDROID_NDK_ROOT

    cygwin安装目录下的“home/当前用户名”的.bash_profile下以UltraEdit(Unix方式)或者eclipse打开,最后添加一句: ANDROID_NDK_ROOT=/cygdr ...

  6. SpringBoot集成Mybatis并具有分页功能PageHelper

    SpringBoot集成Mybatis并具有分页功能PageHelper   环境:IDEA编译工具   第一步:生成测试的数据库表和数据   SET FOREIGN_KEY_CHECKS=0;   ...

  7. 【十大算法实现之naive bayes】朴素贝叶斯算法之文本分类算法的理解与实现

    关于bayes的基础知识,请参考: 基于朴素贝叶斯分类器的文本聚类算法 (上) http://www.cnblogs.com/phinecos/archive/2008/10/21/1315948.h ...

  8. Excel 公式集

    1.  Excel 公式集 按身份证计算年龄 按日计算的 (2018/12/20)(身份证 C2): =TRUNC((DAYS360(CONCATENATE(MID(C2,7,4),"/&q ...

  9. c++ auto 理解

    for (auto i : b) Fuck(i); 是 for (auto bitch = std::begin(b); bitch != std::end(b); bitch++) { auto t ...

  10. java(7)LinkedList源码

    系统环境 JDK1.7 LinkedList的基本结构 :在JDK1.6中LinkedList是双向引用的环形结构,JDK1.6中是双向引用的线性结构 提醒:看链表代码时最好用笔画下链表结构 有助于理 ...