线段树完全版 ~by NotOnlySuccess
线段树讲解(数据结构、C++)

线段树讲解(数据结构、C++)声明:仅⼀张图⽚转载于,⾃⼰画太⿇烦了。
那个博客的讲解也很好,只是他⽤了指针的⽅式来定义线段树,⽽我⽤了结构体,并且他讲了线段树的更⾼级的操作,若对线段树的初级操作不理解,请继续阅读线段树作为⼀种⼗分常⽤的数据结构,在NOIP、NOI中⼴泛的出现,所以在这⾥对线段树进⾏简单的讲解。
线段树⽀持对⼀个数列的求和、单点修改、求最值(最⼤、最⼩)、区间修改(需要lazy标记,暂不讲解)。
这⼏种操作,时间复杂度是(logn)级别的,是⼀种⼗分优秀的数据结构。
因此其获得了⼴泛的应⽤。
定义:顾名思义,它是⼀种树形结构,但每段不是平常所学的⼀个点⼀个点的树,⽽是⼀条⼀条的线段,每条线段包含着⼀些值,其中最主要的是起始和结束点记作 l,r 即左端点和右端点。
那么该如何划分线段树呢?我们采⽤⼆分的思想,即每次将⼀段取半,再进⾏接下来的操作,这样综合了操作的⽅便程度和时间复杂度。
因为线段树通过⼆分得来,所以线段树是⼀颗⼆叉树。
这也⽅便了对⼉⼦查找。
下⾯是线段树的图,有利于理解:建树:仅仅知道模型还是不够的,建树的过程是线段树的关键(build(1,1,n))从⼀号开始,左端是1,右端是n位运算 i<<1 等效于 i/2 (i<<1)|1 等效于 i/2+1 加速。
inline void update(int i)更新i节点维护的值(求和,最⼤……){node[i].sum=node[i<<1].sum+node[(i<<1)|1].sum;node[i].maxx=max(node[i<<1].maxx,node[(i<<1)|1].maxx);}inline void build(int i,int l,int r)//inline 还是加速{node[i].l=l;node[i].r=r;//左右端点为当前递归到的 l 和 rif(l==r){//若l==r 则当前的树节点是真正意义上的点node[i].maxx=a[l];//最⼤值就是本⾝的值node[i].sum=a[l];//区间的和就是本⾝的值return;}int mid=(l+r)/2;//因为是⼆叉树所以以中点为分割点build(i<<1,l,mid);//根据⼆叉树的知识,左⼉⼦是i/2右⼉⼦是i/2+1build((i<<1)|1,mid+1,r);update(i);}数列求和:这是线段树的⼀个典型算法,其他的很多应⽤都是从中转化的。
线段树

线段树转载请注明出处,谢谢!/metalseed/article/details/8039326持续更新中···一:线段树基本概念1:概述线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN)!性质:父亲的区间是[a,b],(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b],线段树需要的空间为数组大小的四倍2:基本操作(demo用的是查询区间最小值)线段树的主要操作有:(1):线段树的构造void build(int node, int begin, int end);主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后回溯的时候给当前节点赋值1.#include <iostream>ing namespace std;3.4.const int maxind = 256;5.int segTree[maxind * 4 + 10];6.int array[maxind];7./* 构造函数,得到线段树 */8.void build(int node, int begin, int end)9.{10. if (begin == end)11. segTree[node] = array[begin]; /* 只有一个元素,节点记录该单元素 */12. else13. {14. /* 递归构造左右子树 */15. build(2*node, begin, (begin+end)/2);16. build(2*node+1, (begin+end)/2+1, end);17.18. /* 回溯时得到当前node节点的线段信息 */19. if (segTree[2 * node] <= segTree[2 * node + 1])20. segTree[node] = segTree[2 * node];21. else22. segTree[node] = segTree[2 * node + 1];23. }24.}25.26.int main()27.{28. array[0] = 1, array[1] = 2,array[2] = 2, array[3] = 4, array[4] = 1, array[5] = 3;29. build(1, 0, 5);30. for(int i = 1; i<=20; ++i)31. cout<< "seg"<< i << "=" <<segTree[i] <<endl;32. return 0;33.}此build构造成的树如图:(2):区间查询int query(int node, int begin, int end, int left, int right);(其中node为当前查询节点,begin,end为当前节点存储的区间,left,right为此次query所要查询的区间)主要思想是把所要查询的区间[a,b]划分为线段树上的节点,然后将这些节点代表的区间合并起来得到所需信息比如前面一个图中所示的树,如果询问区间是[0,2],或者询问的区间是[3,3],不难直接找到对应的节点回答这一问题。
线段数讲义

线段树入门(一)路桥中学陈朝晖今天,我们来介绍一种非常特殊的数据结构——线段树。
首先,来看这个问题:给你n个数,仅有两种操作:(1)给第i个数的值添加x(2)询问区间[a,b]的总和是多少CODEVS 1080 线段树练习时间限制: 1s 空间限制: 128000 KB 题目等级 : 钻石 Diamond题目描述 Description一行N个方格,开始每个格子里都有一个整数。
现在动态地提出一些问题和修改:提问的形式是求某一个特定的子区间[a,b]中所有元素的和;修改的规则是指定某一个格子x,加上或者减去一个特定的值A。
现在要求你能对每个提问作出正确的回答。
1≤N<100000,,提问和修改的总数m<10000条。
输入描述 Input Description输入文件第一行为一个整数N,接下来是1行共n个整数,表示格子中原来的整数。
下面是一个正整数m,接下来有m行,表示m个询问,第一个整数表示询问代号,询问代号1表示增加,后面的两个数x和A表示给位置X上的数值增加A,询问代号2表示区间求和,后面两个整数表示a和b,表示要求[a,b]之间的区间和。
输出描述 Output Description共m行,每个整数样例输入 Sample Input645621341 3 52 1 41 1 92 2 6样例输出 Sample Output2222数据范围及提示 Data Size & Hint1≤N≤100000, m≤10000 。
从题目中所给的数据来看,数据量非常大,所需要的查询次数同样非常多。
#include <iostream>usingnamespace std;int dat[Maxn],N,M;struct Tree{int left,right;longlong sum;}tr[Maxn<<2];//tr[i]表示第i线段树,其中[left,right]表示数据区域的边界,sum表示线段树中当前这一段的总和//数组中的每一个结点都将最终设置在线段树的叶子节点位置上,//而线段树中还存在内部结点的存储。
线段树-敌兵布阵

线段树– NotOnlySuccess 贡献很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时觉得挺自豪的,还去pku打广告,但是现在我自己都不太好意思去看那篇文章了,觉得当时的代码风格实在是太丑了,很多线段树的初学者可能就是看着这篇文章来练习的,如果不小心被我培养出了这么糟糕的风格,实在是过意不去,正好过几天又要给集训队讲解线段树,所以决定把这些题目重新写一遍,顺便把近年我接触到的一些新题更新上去~;并且学习了splay等更高级的数据结构后对线段树的体会有更深了一层,线段树的写法也就比以前飘逸,简洁且方便多了.在代码前先介绍一些我的线段树风格:maxn是题目给的最大区间,而节点数要开4倍,确切的来说节点数要开大于maxn的最小2x的两倍lson和rson分辨表示结点的左儿子和右儿子,由于每次传参数的时候都固定是这几个变量,所以可以用预定于比较方便的表示以前的写法是另外开两个个数组记录每个结点所表示的区间,其实这个区间不必保存,一边算一边传下去就行,只需要写函数的时候多两个参数,结合lson和rson的预定义可以很方便PushUP(int rt)是把当前结点的信息更新到父结点PushDown(int rt)是把当前结点的信息更新给儿子结点rt表示当前子树的根(root),也就是当前所在的结点整理这些题目后我觉得线段树的题目整体上可以分成以下四个部分:单点更新:最最基础的线段树,只更新叶子节点,然后把信息用PushUP(int r)这个函数更新上来hdu1166 敌兵布阵题意:O(-1)思路:O(-1)线段树功能:update:单点增减query:区间求和敌兵布阵Problem DescriptionC国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。
A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。
线段树

对于线段树中的每个结点, 其表示一个区间,我们可以记录和这个区间相关 的一些信息(如最大值、最小值、和等) ,但要满足可二分性,即能直接由其子
结点的相关信息得到。 对于询问区间信息和修改区间信息的操作,线段树一般在能 O(log n) 的时间 内完成,而且常数相对较小(和后面的伸展树比较) ,算是一种高效实用的数据 结构。 (2)线段树的基本操作——查询区间信息 使用线段树的目的就是能高效地查找和修改区间信息, 下面先介绍第一个操 作——查询操作。 对于当前要查询的区间 a, b ,我们从线段树的根结点开始,如果当前结点 表示的区间被查询区间完全包含,那么更新结果,否则分别考察左右子结点,如 果查询区间与某个子结点有交集(也可能两个都有) ,那么就递归考察这个子结 点。代码框架如下1:
对于任意一个区间,会被划分成很多在线段树上存在的区间,可以证明,划 分出来的区间在线段树的每层最多有两个,又因为线段树的深度为 O(log n) ,因 此查询操作的时间复杂度为 O(log n) 。
(3)线段树的基本操作——修改区间信息 相对于查询区间信息,修改区间信息显得稍微复杂一些。
1
本文中的代码均使用 C++语言描述
(4)线段树特点总结 利用线段树, 我们可以高效地询问和修改一个数列中某个区间的信息,并且 代码也不算特别复杂。 但是线段树也是有一定的局限性的, 其中最明显的就是数列中数的个数必须 固定,即不能添加或删除数列中的数。对于这个问题,下面介绍的伸展树就可以 完美的解决。
(7,10]
(0,1] (1,2] (2,3] (3,5] (5,6]
(6,7] (7,8]
(8,10]
(3,4]
线段树ppt课件

•
else Count := 0; 连接处颜色相同并且
非底色,则总数减1
统计算法
最左边的颜色
• end • else if r – l > 1 then
最右边的颜色
• begin
最左颜色=最右颜色=本身
•
result := Count(p * 2, l,非(底l +色则r)统d计iv数加2,1 lc, tl) +
•
else if a >= m then Insert(p * 2 + 1, m, r, a, b, c)
•
else begin
•
Insert(p * 2, l, m, a, m, c);
•
Insert(p * 2 + 1, m, r, m, b, c);
•
end;
•
end;
• end;
• end;
示例
• 初始情况 0 0 0 0 0
• [1,2]
10000
• [3,5]
10110
• [4,6]
10111
• [5,6]
10111
4个1
缺点
• 此方法的时间复杂度决定于下标范围的平 方。
• 当下标范围很大时([0,10000]),此方法 效率太低。
离散化的做法
• 基本思想:先把所有端点坐标从小到大排 序,将坐标值与其序号一一对应。这样便 可以将原先的坐标值转化为序号后,对其 应用前一种算法,再将最后结果转化回来 得解。
Wall
分析
• 这道题目是一个经典的模型。在这里,我 们略去某些处理的步骤,直接分析重点问 题,可以把题目抽象地描述如下:x轴上有 若干条线段,求线段覆盖的总长度。
线段树

换一个问题:
◦ ◦ ◦ ◦ 染色问题 一开始区间内所有的点的颜色都是0 动态将一个区间内的点刷成一个颜色c 动态询问一个点是什么颜色
如何解决?
◦ 仅仅记录对于区间内的修改信息? ◦ 有时间先后问题——麻烦! ◦ 传递信息!——Laziness
再看之前的例子:动态区间和问题
◦ 动态给一个区间内的数加上一个数 ◦ 动态询问一个区间内的数的和
元素的绝对值很小?
◦ 设每个元素绝对值不超过C ◦ O(ClogN)每次修改及询问
出现的问题
◦ 询问区间的众数在任何子区间中都不是众数
信息的合并
◦ 不能丢失信息
区间查询的前提:C=A+B!
◦ 特殊: 查询结果一定是某一个子区间的结果 ◦ 广义: 每个节点维护的信息支持合并
例子A
◦ 单点修改 众数查询 ◦ 特殊:如果众数出现频率不超过1/2则输出BAD
四分树
◦ 优点:书写简单,传递信息方便 ◦ 缺点:复杂度是恶劣的
树套树
◦ 有点:速度快,复杂度优秀 ◦ 缺点:尼玛代码量略大了吧!!!
共同缺点
Anything Else?
◦ 不能很好的合并维护信息——高维数据结构的通病
◦ BIT (Binary Indexed Tree) ◦ 树状数组
给定一个数列 动态修改一个元素的值 动态询问,一个区间[L,R]内的最小数是多少
给定一个数列,数列给定后不会做任何修改 动态询问,一个区间[L,R]内第K小的数的算法 考虑一个每次回答O(lg3n)的在线算法
RGB染色问题
13. 线段树

③ 更新线段树
void insert(int d, int p){ if(tree[p].left==tree[p].right && d==tree[p].left){ tree[p].count +=1; return; } int m=(tree[p].left+tree[p].right)>>1; if(d<=m) insert(d, 2*p); else insert(d, 2*p+1); tree[p].count=tree[2*p].count+tree[2*p+1].count; }
• [0,7]
– [0,7]与[0,7]匹配,[0,7]记录+1
• 询问操作和插入操作类似,也是递归过程: • 2——依次把[0,7],[0,3],[2,3],[2,2]的记 录c加起来,结果为2 • 4——依次把[0,7],[4,7],[4,5],[4,4]的记 录c加起来,结果为3 • 7——依次把[0,7],[4,7],[6,7],[7,7]的记 录c加起来,结果为1
[2,3][3,4]
[5,6]
[6,7] [7,8]
[8,9]
点树的简单应用-第k小数
• 线段树的每个结点表示一个点(任意点a 可以看成是区间[a,a]),称为点树。例如 用于求第k小数的线段树。 • ①结点结构体; • ②创建线段树; • ③更新线段树; • ④查询线段树;
① 结点结构体
struct node{ int left, right; int count; //用于存放此节点值,默认为0 } tree[3*MAXN];
4 例如:杭电1754
• Problem Description • 很多学校流行一种比较的习惯。老师们很 喜欢询问,从某某到某某当中,分数最高 的是多少。这让很多学生很反感。 不管你喜不喜欢,现在需要你做的是,就 是按照老师的要求,写一个程序,模拟老 师的询问。当然,老师有时候需要更新某 位同学的成绩。
HH神总结的线段树专辑_超经典的

46
scanf("%d",&x[i]);
47
sum += query(x[i] , n - 1 , 0 , n - 1 , 1);
48
update(x[i] , 0 , n - 1 , 1);
49
}
50
int ret = sum;
51
for (int i = 0 ; i < n ; i ++) {
52
53 1));
54
else if (op[0] == 'S') update(a , -b , 1 , n , 1);
55
else update(a , b , 1 , n , 1);
56
}
57
}
58
return 0;
}
o hdu1754 I Hate It 题意:O(-1) 思路:O(-1) 线段树功能:update:单点替换 query:区间最值 ?View Code CPP
37
if (R > m) ret += query(L , R , rson);
38
return ret;
39 }
40 int main() {
41
int T , n;
42
scanf("%d",&T);
43
for (int cas = 1 ; cas <= T ; cas ++) {
44
printf("Case %d:\n",cas);
在代码前先介绍一些我的线段树风格:
maxn 是题目给的最大区间,而节点数要开 4 倍,确切的来说节点数要开大于 maxn 的最小 2x 的两倍
线段树

区间更新
const int INFINITE = INT_MAX; const int MAXNUM = 1000; struct SegTreeNode { int val; int addMark;//延迟标记 }segTree[MAXNUM];//定义线段树 /* 功能:构建线段树 root:当前线段树的根节点下标 arr: 用来构造线段树的数组 istart:数组的起始位置 iend:数组的结束位置 */
构造线段树
对于线段树我们可以选择和普通二叉树一样的链式结构。由于线段树是完全 二叉树,我们也可以用数组来存储线段树,节点结构如下(注意到用数组存储时 ,有效空间为2n-1,实际空间确不止这么多,比如上面的线段树中叶子节点1、3 虽然没有左右子树,但是的确占用了数组空间,实际空间是满二叉树的节点数目 。 struct SegTreeNode { int val; }; 一维数组存储线段树时,定义包含n个节点的线段树 SegTreeNode segTree[n],segTree[0]表示根节点。那么对于节点segTree[i],它的左孩子是 segTree[2*i+1],右孩子是segTree[2*i+2]。
查询线段树
那么怎样在它上面超找某个区间的最小值呢?查询的思想是选出一些区间,使 他们相连后恰好涵盖整个查询区间,因此线段树适合解决“相邻的区间的信息 可以被合并成两个区间的并区间的信息”的问题
例如:查询区间[0,3]时,从根节点开始,查询左子树的节点区间[0,2]包含在区间[0,3] 内,返回当前节点的值1;查询右子树时,继续递归查询右子树的左右子树,查询到 非叶节点4时,又要继续递归查询:叶子节点4的节点区间[3,3]包含在查询区间[0,3]内, 返回4,叶子节点9的节点区间[4,4]和[0,3]没有交集,返回INFINITE,因此非叶节点4返 回的是min(4, INFINITE) = 4,叶子节点3的节点区间[5,5]和[0,3]没有交集,返回 INFINITE,因此非叶节点3返回min(4, INFINITE) = 4, 因此根节点返回 min(1,4) = 1。
线段树应用PPT课件

线段树与数组的比较
总结词
线段树在处理复杂查询时优 于数组
详细描述
数组虽然便于随机访问,但 在处理复杂查询如区间最大 值、最小值等问题时,线段 树能够提供更快的查询速度
。
总结词
线段树在处理动态数据集时更具优势
详细描述
当数据集频繁变动时,线段树能够快速地 更新和维护数据,而数组可能需要重新构 建或采取其他复杂的操作。
空间效率
线段树通过节点间的关系,将大 问题分解为小问题,所需空间相 对较小。
线段树的缺点
01
02
03
节点分裂与合并
在线段树进行插入、删除 等操作时,可能会导致节 点分裂或合并,使得树的 平衡性难以维护。
构造与重建
线段树在处理大规模数据 时,可能需要多次重建, 导致时间复杂度较高。
适用场景限制
线段树适用于区间查询问 题,对于其他类型的问题 可能需要其他数据结构或 算法。
线段树应用ppt课件
目录
• 引言 • 线段树的原理 • 线段树的应用实例 • 线段树与其他数据结构的比较 • 线段树的优缺点分析 • 总结与展望
01 引言
什么是线段树
定义
线段树是一种用于处理区间查询问题 的数据结构,它可以在线段上高效地 执行查询、更新和删除操作。
结构
线段树通常由一个根节点和若干个子 节点组成,每个节点包含一个区间的 信息,并且每个节点与其子节点之间 存在一一对应关系。
总结词
数组在随机访问和存储空间方面更具优势
详细描述
数组能够提供快速的随机访问,并且在相 同的数据量下,数组所需的存储空间可能 比线段树更少。
05 线段树的优缺点分析
线段树的优点
高效区间查询
线段树总结——精选推荐

线段树总结之前做了些线段树相关的题⽬,开学⼀段时间后,想着把它整理下,完成了⼤⽜NotOnlySuccess的博⽂“完全版线段树”⾥的⼤部分题⽬,其博⽂地址,然后也加⼊了⾃⼰做过的⼀些题⽬。
整理时,更新了之前的代码风格,不过旧的代码仍然保留着。
同样分成四类,不好归到前四类的都分到了其他。
树状数组能做,线段树都能做(如果是内存限制例外),所以也有些树状数组的题⽬,会标⽰出来,并且放到其他类⾥。
⼀、单点更新1.:有N个兵营,每个兵营都给出了⼈数ai(下标从1开始),有四种命令,(1)”Addij",表⽰第i个营地增加j⼈。
(2)“Sub i j”,表⽰第i个营地减少j⼈。
(3)“Query ij",查询第i个营地到第j个营地的总⼈数。
(4)”End“,表⽰命令结束。
解题报告。
2.:给你N个数,M个操作,操作分两类。
(1)"QAB“,查询区间[A,B]内的最⼤值。
(2)"UAB",将第A个数的值改成B。
解题报告。
3.:给你N个数,要求统计它的所有形式的逆序对的最⼩值。
它的所有形式的意思是,不断将数组开头的第⼀个数放到数组的最后⾯。
解题报告。
4.:有⼀块板,规格为h*w,然后有n张海报,每张海报的规格为1*wi,选择贴海报的位置是:尽量⾼,同⼀⾼度,选择尽量靠左的地⽅。
要求输出每张海报的⾼度位置。
解题报告。
5.:有N个⼈排队,每⼀个⼈都有⼀个val来对应,每⼀个后来⼈都会插⼊当前队伍的某⼀个位置pos。
要求把队伍最后的状态输出。
解题报告。
6.: N个⼩孩围成⼀圈,他们被顺时针编号为 1 到 N。
每个⼩孩⼿中有⼀个卡⽚,上⾯有⼀个⾮ 0 的数字,游戏从第 K 个⼩孩开始,他告诉其他⼩孩他卡⽚上的数字并离开这个圈,他卡⽚上的数字 A 表明了下⼀个离开的⼩孩,如果 A 是⼤于 0 的,则下个离开的是左⼿边第 A 个,如果是⼩于 0 的,则是右⼿边的第 A 个⼩孩。
线段树入门

小结
ቤተ መጻሕፍቲ ባይዱ
其实线段树就是用某个值来代替(概括) 一段区间的值,来避免求解时用到大量的 基础元素。
框架:建树
procedure maketree(k,q,p:longint); //建树,k表示区间[q,p] 的代号 begin if q=p then begin 赋初值 exit; end; maketree(k*2,q,(q+p) div 2); //分割 访问左儿子 代号为 k*2 maketree(k*2+1,(q+p) div 2+1,p); //分割 访问右儿子 代号 为k*2+1 递归回来后由两儿子决定父亲的值 end;
线段树的定义
线段[1, 9]的线段树
[1,9] [1,4] [1,2] 1 2 [3,4] 3 4 [5,9] [5,6] 5 6 [7,9] 7 [8,9] 8 9
1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 8 9
框架:修改基础元素
procedure change(k,q,p,a,s:longint); 修改第a位置的值变为s,[q,p]为当前 查找到的区间 var m:longint; begin if (q>a) or (p<a) then //如果a不在[q,p]中,就退出 exit; if (a=q) and (q=p) then //如果查找到了a,就更新 begin 更新相关量 exit; end; m:=(q+p) div 2; insert(k*2,q,m,a,s); //分割,查找左儿子 insert(k*2+1,m+1,p,a,s); //分割,查找右儿子 用儿子的相关量更新父亲 end;
线段树完整版

线段树【完整版】单点更新:最最基础的线段树,只更新叶子节点,然后把信息用PushUP(int r)这个函数更新上来Hdu1754 I hate it线段树功能:update:单点替换 query:区间最值#include<iostream>#include<algorithm>using namespace std;int MAXN[4000001],d[4000001];void PushUp(int rt){MAXN[rt]=max(MAXN[rt<<1],MAXN[rt<<1|1]);}void build(int l,int r,int rt){if(l==r){cin>> MAXN[rt];return;}int m=(l+r)>>1;build(l,m,rt<<1);build(m+1,r,(rt<<1)+1);PushUp(rt);}void update(int p,int sc,int l,int r,int rt){if(l==r){MAXN[rt]=sc;return;}int m=(l+r)>>1;if(p<=m)update(p,sc,l,m,rt<<1);else update(p,sc,m+1,r,(rt<<1)+1);PushUp(rt);}int getmax(int L,int R,int l,int r,int rt){if(L<=l&&r<=R){return MAXN[rt];}int m=(l+r)>>1;int ret=0;if(L<=m)ret=max(ret,getmax(L,R,l,m,rt<<1));if(R>m)ret=max(ret,getmax(L,R,m+1,r,(rt<<1)+1));return ret;}int main(){int n,m,a,b;char ch;while(cin>>n>>m){build(1,n,1);for(int i=0;i<m;i++){cin>>ch>>a>>b;if(ch=='U')update(a,b,1,n,1);else cout<<getmax(a,b,1,n,1)<<endl;}}}hdu1166敌兵布阵线段树功能:update:单点增减 query:区间求和#include<iostream>#include<string>using namespace std;const int maxn=55555;int sum[maxn<<2];void PushUp(int rt){sum[rt]=sum[rt<<1]+sum[(rt<<1)+1];}void build(int l,int r,int rt){if(l==r){cin>>sum[rt];return;}int m=(l+r)>>1;build(l,m,rt<<1);build(m+1,r,(rt<<1)+1);PushUp(rt);}void update(int p,int add,int l,int r,int rt) {if(l==r){sum[rt]+=add;return;}int m=(l+r)>>1;if(p<=m)update(p,add,l,m,rt<<1);else update(p,add,m+1,r,(rt<<1)+1);PushUp(rt);}int getsum(int L,int R,int l,int r,int rt){if(L<=l&&r<=R){return sum[rt];}int m=(r+l)>>1;int ret=0;if(L<=m)ret+=getsum(L,R,l,m,rt<<1);if(R>m)ret+=getsum(L,R,m+1,r,(rt<<1)+1);return ret;}int main(){int cas,n,a,b,k=0;string s;cin>>cas;while(cas--){k++;cout<<"Case "<<k<<":"<<endl;cin>>n;build(1,n,1);while(cin>>s){if(s=="End")break;if(s=="Query"){cin>>a>>b;cout<<getsum(a,b,1,n,1)<<endl;}else if(s=="Add"){cin>>a>>b;update(a,b,1,n,1);}else{cin>>a>>b;update(a,-b,1,n,1);}}}}hdu1394 Minimum Inversion Number题意:求Inversion后的最小逆序数思路:用O(nlogn)复杂度求出最初逆序数后,就可以用O(1)的复杂度分别递推出其他解线段树功能:update:单点增减 query:区间求和#include<iostream>#include<algorithm>using namespace std;int sum[10001],x[5001];void PushUp(int rt){sum[rt]=sum[rt<<1]+sum[(rt<<1)+1];}void build(int l,int r,int rt){sum[rt]=0;if(l==r)return;int m=(l+r)>>1;build(l,m,rt<<1);build(m+1,r,(rt<<1)+1);}void update(int p,int l,int r,int rt){if(l==r){sum[rt]++;return;}int m=(l+r)>>1;if(p<=m)update(p,l,m,rt<<1);else update(p,m+1,r,(rt<<1)+1);PushUp(rt);}int GetReverse(int L,int R,int l,int r,int rt){if(L<=l&&r<=R)return sum[rt];int m=(l+r)>>1;int ret=0;if(L<=m)ret+=GetReverse(L,R,l,m,rt<<1);if(R>m)ret+=GetReverse(L,R,m+1,r,(rt<<1)+1);return ret;}int main(){int n;while(cin>>n){build(0,n-1,1);int sum=0;for(int i=0;i<n;i++){cin>>x[i];sum+=GetReverse(x[i],n-1,0,n-1,1);//求x[i]到n-1之间已存在几个数字,即求x[i]的逆序数update(x[i],0,n-1,1);//包含[x[i],x[i]]的区间+1}int ret=sum;for(int i=0;i<n;i++){//把第一个位子上的数(x[i])移到最后,则被移的数的逆序是n-1-x[i],而未被移的数字有x[i]个比x[i]小sum+=n-1-x[i]-x[i];ret=min(sum,ret);}cout<<ret<<endl;}}hdu2795Billboard题意:h*w的木板,放进一些1*L的物品,求每次放空间能容纳且最上边的位子思路:每次找到最大值的位子,然后减去L线段树功能:query:区间求最大值的位子(直接把update的操作在query里做了)#include<iostream>#include<algorithm>using namespace std;int MAX[222222<<2];int w;void PushUp(int rt){MAX[rt]=max(MAX[rt<<1],MAX[(rt<<1)|1]);}void build(int l,int r,int rt){MAX[rt]=w;//c初始化每行都有W的空间if(l==r)return;int m=(l+r)>>1;build(l,m,rt<<1);build(m+1,r,(rt<<1|1));}int query(int x,int l,int r,int rt){if(l==r){MAX[rt]-=x;return l;}int m=(l+r)>>1;int ret=(MAX[rt<<1] >= x? query(x ,l,m,rt<<1):query(x ,m+1,r,(rt<<1)|1)) ;//查找左边能放得下,优先左边PushUp(rt);return ret;}int main(){int h,n,x;while(scanf("%d%d%d",&h,&w,&n)==3){if(h>n)h=n;//题目中h>1,n<200000,h可能会大于所开的MAX[];build(1,h,1);for(int i=0;i<n;i++){scanf("%d",&x);if(x>MAX[1])printf("%d\n",-1);else printf("%d\n",query(x,1,h,1));}}}成段更新(通常这对初学者来说是一道坎),需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候hdu1698Just a Hook题意:O(-1)思路:O(-1)线段树功能:update:成段替换 (由于只query一次总区间,所以可以直接输出1结点的信息)#include <cstdio>#include <algorithm>using namespace std;const int maxn = 111111;int col[maxn<<2];int sum[maxn<<2];void PushUp(int rt){sum[rt]=sum[rt<<1]+sum[(rt<<1)|1];}void PushDown(int rt,int m){if(col[rt]){col[(rt<<1)|1]=col[rt<<1]=col[rt];sum[rt<<1]=col[rt]*(m-(m>>1));sum[(rt<<1)|1]=col[rt]*(m>>1);col[rt]=0;}}void build(int l,int r,int rt){col[rt]=0;sum[rt]=1;if(l==r)return;int m=(l+r)>>1;build(l,m,rt<<1);build(m+1,r,(rt<<1)|1);PushUp(rt);}void update(int L,int R,int c,int l,int r,int rt){if(L<=l&&r<=R){col[rt]=c;sum[rt]=(r-l+1)*c;return;}PushDown(rt,r-l+1);int m=(l+r)>>1;if(L<=m)update(L,R,c,l,m,rt<<1);if(R>m)update(L,R,c,m+1,r,(rt<<1)|1);PushUp(rt);}int main(){int cas,n,m,a,b,c,k=0;scanf("%d",&cas);while(cas--){k++;scanf("%d",&n);build(1,n,1);scanf("%d",&m);while(m--){scanf("%d%d%d",&a,&b,&c);update(a,b,c,1,n,1);}printf("Case %d: The total value of the hook is %d.\n",k,sum[1]); }}3468A Simple Problem with Integers线段树功能:update:成段增减 query:区间求和#include<iostream>#include<algorithm>using namespace std;const int maxn = 111111;long long int add[maxn<<2];long long int sum[maxn<<2];void PushUp(int rt){sum[rt]=sum[rt<<1]+sum[(rt<<1)|1];}void PushDown(int rt,int m){if(add[rt]){add[rt<<1]+=add[rt];add[(rt<<1)|1]+=add[rt];sum[rt<<1]+=add[rt]*(m-(m>>1));sum[(rt<<1)|1]+=add[rt]*(m>>1);add[rt]=0;}}void build(int l,int r,int rt){add[rt]=0;if(l==r){cin>>sum[rt];return;}int m=(l+r)>>1;build(l,m,rt<<1);build(m+1,r,(rt<<1)|1);PushUp(rt);}void update(int L,int R,int c,int l,int r,int rt){if(L<=l&&r<=R){add[rt]+=c;sum[rt]+=(long long int)(r-l+1)*c;return;}PushDown(rt,r-l+1);int m=(l+r)>>1;if(L<=m)update(L,R,c,l,m,rt<<1);if(R>m)update(L,R,c,m+1,r,(rt<<1)|1);PushUp(rt);}long long int query(int L,int R,int l,int r,int rt){if(L<=l&&r<=R)return sum[rt];PushDown(rt,r-l+1);int m=(l+r)>>1;long long int ret=0;if(L<=m)ret+=query(L,R,l,m,rt<<1);if(m<R)ret+=query(L,R,m+1,r,(rt<<1)|1);return ret;}int main(){int n,m,a,b,c;char ch;while(cin>>n>>m){build(1,n,1);for(int i=0;i<m;i++){cin>>ch;if(ch=='C'){cin>>a>>b>>c;update(a,b,c,1,n,1);}else {cin>>a>>b;cout<<query(a,b,1,n,1)<<endl;}}}}poj2528Mayor's posters题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报思路:这题数据范围很大,直接搞超时+超内存,需要离散化:离散化简单的来说就是只取我们需要的值来用,比如说区间[1000,2000],[1990,2012] 我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,所以我只需要1000,1990,2000,2012就够了,将其分别映射到0,1,2,3,在于复杂度就大大的降下来了所以离散化要保存所有需要用到的值,排序后,分别映射到1~n,这样复杂度就会小很多很多而这题的难点在于每个数字其实表示的是一个单位长度(并且一个点),这样普通的离散化会造成许多错误(包括我以前的代码,poj这题数据奇弱)给出下面两个简单的例子应该能体现普通离散化的缺陷:1-10 1-4 5-101-10 1-4 6-10为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,2,6,10]如果相邻数字间距大于1的话,在其中加上任意一个数字,比如加成[1,2,3,6,7,10],然后再做线段树就好了.线段树功能:update:成段替换 query:简单hash#include<iostream>#include<algorithm>#include<cstring>using namespace std;const int maxn=11111;int col[maxn<<4],le[maxn],ri[maxn],X[maxn*3],hash[maxn];int ans;void PushDown(int rt){if(col[rt]!=-1){col[rt<<1]=col[(rt<<1)|1]=col[rt];col[rt]=-1;}}void update(int L,int R,int c,int l,int r,int rt){if(L<=l&&r<=R){col[rt]=c;return;}PushDown(rt);int m=(l+r)>>1;if(L<=m)update(L,R,c,l,m,rt<<1);if(R>m)update(L,R,c,m+1,r,(rt<<1)|1);}void query(int l,int r,int rt){if(col[rt]!=-1){if(!hash[col[rt]]){ans++;hash[col[rt]]=1;}return;}if(l==r)return;int m=(l+r)>>1;query(l,m,rt<<1);query(m+1,r,(rt<<1)|1);}int Bin(int key,int n){int l=0,r=n-1;while(l<=r){int m=(l+r)>>1;if(X[m]==key)return m;if(X[m]>key) r=m-1;else l=m+1;}return -1;}int main(){int cas,n;cin>>cas;while(cas--){cin>>n;int nn=0;for(int i=0;i<n;i++){cin>>le[i]>>ri[i];X[nn++]=le[i],X[nn++]=ri[i];}sort(X,X+nn);int m=1;for(int i=1;i<nn;i++)if(X[i]!=X[i-1])X[m++]=X[i];for(int i=m-1;i>0;i--)if(X[i]!=X[i-1]+1)X[m++]=X[i-1]+1;sort(X,X+m);memset(col,-1,sizeof(col));for(int i=0;i<n;i++){int l=Bin(le[i],m);int r=Bin(ri[i],m);update(l,r,i,0,m,1);}ans=0;memset(hash,0,sizeof(hash));query(0,m,1);cout<<ans<<endl;}}区间合并这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并poj3667Hotel题意:1 a:询问是不是有连续长度为a的空房间,有的话住进最左边2 a b:将[a,a+b-1]的房间清空思路:记录区间中最长的空房间线段树操作:update:区间替换 query:询问满足条件的最左断点#include<iostream>#include<algorithm>using namespace std;const int maxn=50000;int cover[maxn<<2];int lsum[maxn<<2],rsum[maxn<<2],msum[maxn<<2];void PushUp(int rt,int m){lsum[rt]=lsum[rt<<1],rsum[rt]=rsum[(rt<<1)|1];if(lsum[rt<<1]==m-(m>>1))lsum[rt]+=lsum[(rt<<1)|1];if(rsum[(rt<<1)|1]==m>>1)rsum[rt]+=rsum[rt<<1];msum[rt]=max(max(msum[rt<<1],msum[(rt<<1)|1]),rsum[rt<<1]+lsum[(rt<<1)|1]); }void PushDown(int rt,int m){if(cover[rt]!=-1){cover[rt<<1]=cover[(rt<<1)|1]=cover[rt];lsum[rt<<1]=rsum[rt<<1]=msum[rt<<1]=(cover[rt]? 0:m-(m>>1));lsum[(rt<<1)|1]=rsum[(rt<<1)|1]=msum[(rt<<1)|1]=(cover[rt]? 0:m>>1);cover[rt]=-1;}}void build(int l,int r,int rt){cover[rt]=-1;lsum[rt]=rsum[rt]=msum[rt]=r-l+1;if(l==r)return;int m=(l+r)>>1;build(l,m,rt<<1);build(m+1,r,(rt<<1)|1);}void update(int L,int R,int c,int l,int r,int rt){if(L<=l&&r<=R){cover[rt]=c;lsum[rt]=rsum[rt]=msum[rt]=(c? 0:r-l+1);return;}PushDown(rt,r-l+1);int m=(l+r)>>1;if(L<=m)update(L,R,c,l,m,rt<<1);if(R>m)update(L,R,c,m+1,r,(rt<<1)|1);PushUp(rt,r-l+1);}int query(int w,int l,int r,int rt){if(l==r)return l;PushDown(rt,r-l+1);int m=(l+r)>>1;if(msum[rt<<1]>=w)return query(w,l,m,rt<<1);else if(rsum[rt<<1]+lsum[(rt<<1)|1]>=w)return m-rsum[rt<<1]+1;else query(w,m+1,r,(rt<<1)|1);}int main(){int n,m,a,b,c;while(cin>>n>>m){build(1,n,1);while(m--){cin>>a;if(a==1){cin>>b;if(msum[1]<b){cout<<0<<endl;continue;}int ans=query(b,1,n,1);cout<<ans<<endl;update(ans,ans+b-1,1,1,n,1);}else{cin>>b>>c;update(b,b+c-1,0,1,n,1);}}}}hdu3911Black And White题意:给出01串 1代表黑色 0代表白色0 a b:询问[a,b]中1的连续最大长度1 a,b:把[a,b]区间的0->1 1->0思路:lsum1[],rsum1[],msum1[]分别表示区间做起连续1的最大长度,右起连续1的最大长度,区间1的最大连续长度lsum0[],rsum0[],msum0[]分别表示区间做起连续0的最大长度,右起连续0的最大长度,区间连续0的最大连续长度0 a b:交换 0和1的lsum[] rsum[] msum[]; ROX[]表示需要向儿子更新两次更新=不变线段树功能:update区间替换,query询问区间1的最大连续长度#include<iostream>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=100001;int ROX[maxn<<2];int lsum1[maxn<<2],rsum1[maxn<<2],msum1[maxn<<2];int lsum0[maxn<<2],rsum0[maxn<<2],msum0[maxn<<2];void PushUp(int rt,int m){lsum1[rt]=lsum1[rt<<1],rsum1[rt]=rsum1[(rt<<1)|1];if(lsum1[rt<<1]==m-(m>>1))lsum1[rt]+=lsum1[(rt<<1)|1];if(rsum1[(rt<<1)|1]==m>>1)rsum1[rt]+=rsum1[rt<<1];msum1[rt]=max(max(msum1[rt<<1],msum1[(rt<<1)|1]),rsum1[rt<<1]+lsum1[(rt<<1)|1]);lsum0[rt]=lsum0[rt<<1],rsum0[rt]=rsum0[(rt<<1)|1];if(lsum0[rt<<1]==m-(m>>1))lsum0[rt]+=lsum0[(rt<<1)|1];if(rsum0[(rt<<1)|1]==m>>1)rsum0[rt]+=rsum0[rt<<1];msum0[rt]=max(max(msum0[rt<<1],msum0[(rt<<1)|1]),rsum0[rt<<1]+lsum0[(rt<<1)|1]);}void PushDown(int rt,int m){if(ROX[rt]){ROX[rt<<1]^=1,ROX[(rt<<1)|1]^=1;swap(lsum0[rt<<1],lsum1[rt<<1]),swap(rsum1[rt<<1],rsum0[rt<<1]),swap(msum1[rt<<1],msum0[rt<<1]); swap(lsum0[(rt<<1)|1],lsum1[(rt<<1)|1]), swap(rsum1[(rt<<1)|1],rsum0[(rt<<1)|1]),swap(msum1[(rt<<1)|1],msum0[(rt<<1)|1]);ROX[rt]=0;}}void build(int l,int r,int rt){ROX[rt]=0;if(l==r){int c;scanf("%d",&c);lsum1[rt]=rsum1[rt]=msum1[rt]=c;lsum0[rt]=rsum0[rt]=msum0[rt]=c^1;return;}int m=(l+r)>>1;build(l,m,rt<<1);build(m+1,r,(rt<<1)|1);PushUp(rt,r-l+1);}void update(int L,int R,int l,int r,int rt){if(L<=l&&r<=R){ROX[rt]^=1;swap(lsum0[rt],lsum1[rt]),swap(rsum1[rt],rsum0[rt]),swap(msum1[rt],msum0[rt]);return;}PushDown(rt,r-l+1);int m=(l+r)>>1;if(L<=m)update(L,R,l,m,rt<<1);if(R>m)update(L,R,m+1,r,(rt<<1)|1);PushUp(rt,r-l+1);}int query(int L,int R,int l,int r,int rt){if(L<=l&&r<=R)return msum1[rt];int tmp;PushDown(rt,r-l+1);int m=(l+r)>>1;if(m>=R) tmp=query(L,R,l,m,rt<<1);else if(m<L)tmp=query(L,R,m+1,r,(rt<<1)|1);elsetmp=max(query(L,R,l,m,rt<<1),query(L,R,m+1,r,(rt<<1)|1));int tmp1=min(m-L+1,rsum1[rt<<1]);int tmp2=min(R-m,lsum1[(rt<<1)|1]);tmp=max(tmp,tmp1+tmp2);}return tmp;}int main(){int n,m,a,b,c;while(scanf("%d",&n)==1){build(1,n,1);scanf("%d",&m);while(m--){scanf("%d%d%d",&a,&b,&c);if(a==1)update(b,c,1,n,1);else cout<<query(b,c,1,n,1)<<endl;}}}扫描线这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去最典型的就是矩形面积并,周长并等题hdu1542Atlantis题意:矩形面积并思路:浮点数先要离散化;然后把矩形分成两条边,上边和下边,对横轴建树,然后从下到上扫描上去,用cnt表示该区间下边比上边多几个线段树操作:update:区间增减 query:直接取根节点的值#include<iostream>#include<algorithm>#include<cstring>#include<iomanip>using namespace std;const int maxn=201;int cnt[maxn<<2];double X[maxn<<1],sum[maxn<<2];struct Seg{double l,r,h;int c;Seg(){};Seg(double _l,double _r,double _h,int _c):l(_l),r(_r),h(_h),c(_c){};bool operator <(const Seg& cmp){return h<cmp.h;}}s[maxn<<2];void PushUp(int rt,int l,int r){if(cnt[rt])sum[rt]=X[r+1]-X[l];else if(l==r)sum[rt]=0;else sum[rt]=sum[rt<<1]+sum[(rt<<1)|1];void update(int L,int R,int c,int l,int r,int rt){if(L<=l&&r<=R){cnt[rt]+=c;PushUp(rt,l,r);return;}int m=(l+r)>>1;if(L<=m)update(L,R,c,l,m,rt<<1);if(R>m)update(L,R,c,m+1,r,(rt<<1)|1);PushUp(rt,l,r);}int Bin(double key,int n){int l=0,r=n-1;while(l<=r){int m=(l+r)>>1;if(X[m]==key)return m;if(key<X[m])r=m-1;else l=m+1;}return -1;}int main(){cout.precision(2);int n,cas=0;double a,b,c,d;while(cin>>n&&n){cas++;int m=0;for(int i=0;i<n;i++){cin>>a>>b>>c>>d;s[m]=Seg(a,c,b,1);X[m++]=a;s[m]=Seg(a,c,d,-1);X[m++]=c;}sort(X,X+m);sort(s,s+m);int k=1;double ans=0;for(int i=1;i<m;i++)if(X[i]!=X[i-1])X[k++]=X[i];memset(cnt,0,sizeof(cnt));memset(sum,0,sizeof(sum));for(int i=0;i<m;i++){int l=Bin(s[i].l,k);int r=Bin(s[i].r,k)-1;if(l <= r) update(l,r,s[i].c,0,k-1,1);ans+=sum[1]*(s[i+1].h-s[i].h);}cout<<"Test case #"<<cas<<endl;cout<<"Total explored area: "<<fixed<<ans<<endl;cout<<endl;}}hdu1828Picture题意:矩形周长并思路:与面积不同的地方是还要记录竖的边有几个(numseg记录),并且当边界重合的时候需要合并(用lbd和rbd表示边界来辅助)线段树操作:update:区间增减 query:直接取根节点的值#include<iostream>#include<algorithm>#include<cmath>using namespace std;const int maxn=20001;int len[maxn<<2],lbd[maxn<<2],rbd[maxn<<2],cnt[maxn<<2],numSeg[maxn<<2];struct Seg{int l,r,h,c;Seg(){};Seg(int _l,int _r,int _h,int _c):l(_l),r(_r),h(_h),c(_c){};bool operator<(Seg&a){return h<a.h;}}s[maxn];void PushUp(int rt,int l,int r){if(cnt[rt]){lbd[rt]=rbd[rt]=1;len[rt]=r-l+1;numSeg[rt]=2;}else if(l==r){lbd[rt]=rbd[rt]=len[rt]=numSeg[rt]=0;}else{lbd[rt]=lbd[rt<<1],rbd[rt]=rbd[(rt<<1)|1];len[rt]=len[rt<<1]+len[(rt<<1)|1];numSeg[rt]=numSeg[rt<<1]+numSeg[(rt<<1)|1];if(rbd[rt<<1]&&lbd[(rt<<1)|1])numSeg[rt]-=2;}}void update(int L,int R,int c,int l,int r,int rt){if(L<=l&&r<=R){cnt[rt]+=c;PushUp(rt,l,r);return;}int m=(l+r)>>1;if(L<=m)update(L,R,c,l,m,rt<<1);if(R>m)update(L,R,c,m+1,r,(rt<<1)|1);PushUp(rt,l,r);}int main(){int n,a,b,c,d;while(cin>>n){int m=0,lmin=10000,rmax=-10000;for(int i=0;i<n;i++)cin>>a>>b>>c>>d;lmin=min(lmin,a);rmax=max(rmax,c);s[m++]=Seg(a,c,b,1);s[m++]=Seg(a,c,d,-1);}sort(s,s+m);int ret=0,last=0;for(int i=0;i<m;i++){if(s[i].l < s[i].r)update(s[i].l,s[i].r-1,s[i].c,lmin,rmax-1,1);ret+=numSeg[1]*(s[i+1].h-s[i].h);ret+=abs(len[1]-last);last=len[1];}cout<<ret<<endl;}}。
线段叠加——线段树

线段叠加——线段树2006/11/10 下午07:40我们往往遇到这样的问题,研究的对象是数量庞大的线段,普通的方法往往是O(n^2),的,而用线段树我们可以做到O(nlogn)。
线段树是一棵完全二叉树,它的根节点代表一个区间[a,b],左结点是[a,(a+b) div 2],右结点是[(a+b) div 2,b],且左右结点分别是线段树,注意我们这里说的是线段,[a,b]即表示数轴上a,b两点间的线段,所以左结点和右结点并没有交。
线段数的基本操作有插入、查找、删除,而插入删除的操作都是建立在查找的基础上的:查找线段[a,b]根据当前结点的边界中点把[a,b]分成最多2部分,然后递归查找它的左右结点。
删除[a,b]找到[a,b]后,将其结点的相关数据清除即可,不需要删除结点。
插入[a,b]找到[a,b]后,添加其结点的相关数据。
相关数据依题目的要求不同而不同,这里不一一列举。
因为线段树是一棵完全二叉树,所以可以用数组模拟,父结点s,左孩子s*2,右孩子s*2+1。
线段树的复杂度就是查找的复杂度,不难证明每层最多只访问2个结点,而总层数是log(b-a),所以复杂度是O(2log(b-a))=O(logn)Const Maxn=120000; {最多支持120000条线段,即支持Long_x<=60000}Type TreeNode=Recorda,b,Left,Right:Longint;Cover,bj:shortint; {记录线段是否被覆盖;线段的标记}End;V ar i,j,k,m,n,tot,c,d,Ans:longint;Tree:array[0..Maxn] of TreeNode;Procedure MakeTree(a,b:Longint); {建立线段树的过程}V ar Now:longint;Begininc(tot);Now:=tot;Tree[Now].a:=a;Tree[Now].b:=b;if a+1<b thenbeginTree[Now].Left:=tot+1;MakeTree(a,(a+b) shr 1);Tree[Now].Right:=tot+1;MakeTree((a+b) shr 1,b);end;End;Procedure Init; {读入数据并预处理的过程}Beginassign(input,'sample1.in');reset(input);assign(output,'sample1.out');rewrite(output);readln(n,m);fillchar(Tree,sizeof(Tree),0);tot:=0;MakeTree(1,n);End;Procedure Clean(Num:Longint); {更新标记的过程}BeginTree[Num].Cover:=0;Tree[Num].bj:=0;Tree[Tree[Num].Left].bj:=-1; Tree[Tree[Num].Right].bj:=-1;End;Procedure Insert(Num,c,d:longint); {涂色的过程}V ar Mid:longint;Beginif Tree[Num].bj=-1 then Clean(Num); {若被标记则更新}if Tree[Num].Cover=1 then exit; {若线段已被涂色,退出过程}if (c<=Tree[Num].a)and(d>=Tree[Num].b) thenbeginTree[Num].Cover:=1;exit;end;Mid:=(Tree[Num].a+Tree[Num].b) shr 1;if c<Mid then Insert(Tree[Num].Left,c,d);if d>Mid then Insert(Tree[Num].Right,c,d);End;Procedure Delete(Num,c,d:Longint); {擦除颜色的过程}V ar Mid:Longint;Beginif Tree[Num].bj=-1 then Exit; {若线段被标记,说明该线段已不复存在,无需再进行删除,退出过程}if (c<=Tree[Num].a)and(d>=Tree[Num].b) thenbeginTree[Num].Cover:=0;Tree[Tree[Num].Left].bj:=-1;Tree[Tree[Num].Right].bj:=-1;{把线段已被删除的信息传给左右儿子}exit;end;if Tree[Num].Cover=1 then {若该线段是被涂了色的}beginTree[Num].Cover:=0;Tree[Tree[Num].Left].bj:=-1;Tree[Tree[Num].Right].bj:=-1; {先删除}if Tree[Num].a<c then Insert(Num,Tree[Num].a,c); {再插入}if d<Tree[Num].b then Insert(Num,d,Tree[Num].b);endelse {否则继续对左右儿子调用删除过程}beginMid:=(Tree[Num].a+Tree[Num].b) shr 1;if c<Mid then Delete(Tree[Num].Left,c,d);if d>Mid then Delete(Tree[Num].Right,c,d);end;End;Procedure Calculate(Num:longint);{计算被覆盖的单位线段条数的过程}Beginif Num=0 then exit; {父亲已是叶子结点了(叶子节点的儿子为0),返回}if Tree[Num].bj=-1 then exit; {线段被标记,说明已不存在,返回}if Tree[Num].Cover=1 thenbegininc(Ans,Tree[Num].b-Tree[Num].a);exit;end;Calculate(Tree[Num].Left);Calculate(Tree[Num].Right);End;Procedure Main; {主过程}V ar i,k:longint;Beginfor i:=1 to m dobeginreadln(k,c,d);if k=1 then Insert(1,c,d)else Delete(1,c,d);end;Ans:=0;Calculate(1);Writeln(Ans);Close(output);End;Begin {主程序}Init;Main;End.。
线段树_刘汝佳(有版权)

SUM的计算
• 右图表示影响 SUM(7, 9)的所 有区间
– 影响全部: [1,9], [5,9], [7,9] – 影响部分: 7, [8,9], 8, 9
[1,2] 1 2 [1,9] [1,4] [3,4] 3 4 [5,9] [5,6] 5 6 [7,9] 7 [8,9] 8 9
完整的算法
– 得到讨论区间(可能要先离散化) – 设计区间附加信息和维护/统计算法
• 线段树自身没有任何数据, 不像BST一样有 一个序关系 • 警告 想清楚 警告: 想清楚附加信息的准确含义 不能有 准确含义, 准确含义 半点含糊! • 建议 先设计便于解题 建议: 便于解题的附加信息,如果难 便于解题 以维护就加以修改
[3,4]
5 3 3 5 5 1 1
[4,5]
5 3 3 5
1 2 2 3 3 4 4 5
1 2 2 3 3 4 4 5
1 2 2 3 3 4 4
1 2 2 3 3 4 4 5
矩形树
• 每个矩形分成四份
– 空间复杂度:XY – 时间复杂度:X+Y
(x2,y2) (1,1)
(4,3)
Son1
Son2
– ADD: 给i对应结点的所有直系祖先s值增加k – SUM: 做区间分解, 把对应结点的s值相加
动态统计问题II
• 包含n个元素的数组A
– ADD(i, j, k): 给A[i], A[i+1], … A[j]均增加k – QUERY(i): 求A[i]
• 先看看是否可以沿用刚才的附加信息
– QUERY(i)就是读取i对应的结点上的s值 – ADD呢? 极端情况下, 如果是修改整个区间, 则 所有结点都需要修改!
zkw线段树讲稿 统计的力量 线段树

* n = n >> 1; * Tree n = Tree 2n + Tree 2n+1 ;
清华大学 张昆玮
*修改就更简单
34
2022/10/14
*for T n+=M =V, n>>=1;n;n>>=1
* T n =T n+n +T n+n+1 ;
*没了 *没了,
清华大学 张昆玮
*C语言更简单
35
2022/10/14
*仅使用了两倍原数组的空间 *其中还完整地包括了原数组 *构造容易:
* For i=M-1 downto 1 do T i =T 2i +T 2i+1 ;
*太好写了 好理解 *自底向上,只访问一次,而且不一定访问到顶层 *实践中非常快,与树状数组接近 *为什么呢 后面再讲,
M x +=A,M 2x -=A,M 2x+1 -=A
*什么意思
清华大学 张昆玮
54
2022/10/14
32
2022/10/14
*如果需要查询 0 就整个向后平移一下 *所有下标加一
*如果需要在 0,1024 中查询1023结尾的区间 *慢 你的数据规模不是 1000 么 *…… *如果真的要到1023,直接把总区间变成 0,2048 *就是这么狠
*不要紧张
清华大学 张昆玮
33
2022/10/14
*Func Change n,NewValue
清华大学 张昆玮
*一些简单的问题
23
线断树

线段树基础知识从简单说起,线段树其实可以理解成一种特殊的二叉树。
但是这种二叉树较为平衡,和静态二叉树一样,都是提前已经建立好的树形结构。
针对性强,所以效率要高。
这里又想到了一句题外话:动态和静态的差别。
动态结构较为灵活,但是速度较慢;静态结构节省内存,速度较快。
接着回到线段树上来,线段树是建立在线段的基础上,每个结点都代表了一条线段[a , b]。
长度为1的线段成为元线段。
非元线段都有两个子结点,左结点代表的线段为[a , (a + b ) / 2],右结点代表的线段为[( a + b ) / 2 , b]。
图一就是一棵长度范围为[1 , 10]的线段树。
长度范围为[1 , L] 的一棵线段树的深度为log ( L - 1 ) + 1。
这个显然,而且存储一棵线段树的空间复杂度为O(L)。
线段树支持最基本的操作为插入和删除一条线段。
下面已插入为例,详细叙述,删除类似。
将一条线段[a , b] 插入到代表线段[l , r]的结点p中,如果p不是元线段,那么令mid=(l+r)/2。
如果a<mid,那么将线段[a , b] 也插入到p的左儿子结点中,如果b>mid,那么将线段[a , b] 也插入到p的右儿子结点中。
插入(删除)操作的时间复杂度为O (Log n )。
上面的都是些基本的线段树结构,但只有这些并不能做什么,就好比一个程序有输入没输出,根本没有任何用处。
最简单的应用就是记录线段有否被覆盖,并随时查询当前被覆盖线段的总长度。
那么此时可以在结点结构中加入一个变量int count;代表当前结点代表的子树中被覆盖的线段长度和。
这样就要在插入(删除)当中维护这个count值,于是当前的覆盖总值就是根节点的count值了。
另外也可以将count换成bool cover;支持查找一个结点或线段是否被覆盖。
例题1(ZJU1610 Count The Colors 线段树基本应用题目)给出在线段[0,8000]上的若干次涂色,问最后能看见哪些颜色,并统计能看到多少段。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
线段树完全版 ~by NotOnlySuccess 很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时觉得挺自豪的,还去pku 打广告,但是现在我自己都不太好意思去看那篇文章了,觉得当时的代码风格实在是太丑了,很多线段树的初学者可能就是看着这篇文章来练习的,如果不小心被我培养出了这么糟糕的风格,实在是过意不去,正好过几天又要给集训队讲解线段树,所以决定把这些题目重新写一遍,顺便把近年我接触到的一些新题更新上去~;并且学习了splay 等更高级的数据结构后对线段树的体会有更深了一层,线段树的写法也就比以前飘逸,简洁且方便多了.在代码前先介绍一些我的线段树风格: ∙maxn 是题目给的最大区间,而节点数要开4倍,确切的来说节点数要开大于maxn 的最小2x 的两倍 ∙lson 和rson 分辨表示结点的左儿子和右儿子,由于每次传参数的时候都固定是这几个变量,所以可以用预定于比较方便的表示 ∙以前的写法是另外开两个个数组记录每个结点所表示的区间,其实这个区间不必保存,一边算一边传下去就行,只需要写函数的时候多两个参数,结合lson 和rson 的预定义可以很方便 ∙PushUP(int rt)是把当前结点的信息更新到父结点 ∙PushDown(int rt)是把当前结点的信息更新给儿子结点 ∙ rt 表示当前子树的根(root),也就是当前所在的结点整理这些题目后我觉得线段树的题目整体上可以分成以下四个部分: ∙ 单点更新:最最基础的线段树,只更新叶子节点,然后把信息用PushUP(int r)这个函数更新上来 o hdu1166 敌兵布阵题意:O(-1)思路:O(-1)线段树功能:update:单点增减 query:区间求和?View Code CPP1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <cstdio>#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 55555;int sum [maxn <<2];void PushUP (int rt ) {sum [rt ] = sum [rt <<1] + sum [rt <<1|1];}void build (int l,int r,int rt ) {if (l == r ) {scanf ("%d",&sum [rt ]);return ;}int m = (l + r ) >> 1; build (lson );171819202122232425262728293031323334353637383940414243444546474849505152535455565758 build(rson); PushUP(rt);}void update(int p,int add,int l,int r,int rt){if(l == r){sum[rt]+= add;return;}int m =(l + r)>>1;if(p <= m) update(p , add , lson);else update(p , add , rson);PushUP(rt);}int query(int L,int R,int l,int r,int rt){if(L <= l && r <= R){return sum[rt];}int m =(l + r)>>1;int ret =0;if(L <= m) ret += query(L , R , lson);if(R > m) ret += query(L , R , rson);return ret;}int main(){int T , n;scanf("%d",&T);for(int cas =1; cas <= T ; cas ++){printf("Case %d:\n",cas);scanf("%d",&n);build(1 , n , 1);char op[10];while(scanf("%s",op)){if(op[0]=='E')break;int a , b;scanf("%d%d",&a,&b);if(op[0]=='Q')printf("%d\n",query(a , b , 1 , n , 1));else if(op[0]=='S') update(a , -b , 1 , n , 1);else update(a , b , 1 , n , 1);}}return0;}o hdu1754 I Hate It题意:O(-1)思路:O(-1)线段树功能:update:单点替换 query:区间最值?View Code CPP1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <cstdio>#include <algorithm>using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 222222;int MAX [maxn <<2];void PushUP (int rt ) {MAX [rt ] = max (MAX [rt <<1] , MAX [rt <<1|1]);}void build (int l,int r,int rt ) {if (l == r ) {scanf ("%d",&MAX [rt ]);return ;}int m = (l + r ) >> 1;build (lson );build (rson );PushUP (rt ); }void update (int p,int sc,int l,int r,int rt ) {if (l == r ) {MAX [rt ] = sc ;return ;}int m = (l + r ) >> 1;if (p <= m ) update (p , sc , lson );else update (p , sc , rson );PushUP (rt ); }int query (int L,int R,int l,int r,int rt ) {if (L <= l && r <= R ) {return MAX [rt ];}int m = (l + r ) >> 1;int ret = 0;if (L <= m ) ret = max (ret , query (L , R , lson ));if (R > m ) ret = max (ret , query (L , R , rson ));return ret ; }42 43 44 45 46 47 48 49 5051 52 53 54 55 int main () {int n , m ;while (~scanf ("%d%d",&n,&m )) {build (1 , n , 1);while (m --) {char op [2];int a , b ;scanf ("%s%d%d",op,&a,&b ); if (op [0] == 'Q') printf ("%d \n ",query (a , b , 1 , n , 1));else update (a , b , 1 , n , 1);}}return 0; } o hdu1394 Minimum Inversion Number题意:求Inversion 后的最小逆序数思路:用O(nlogn)复杂度求出最初逆序数后,就可以用O(1)的复杂度分别递推出其他解 线段树功能:update:单点增减 query:区间求和?View Code CPP1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <cstdio>#include <algorithm>using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 5555;int sum [maxn <<2];void PushUP (int rt ) {sum [rt ] = sum [rt <<1] + sum [rt <<1|1];}void build (int l,int r,int rt ) {sum [rt ] = 0;if (l == r ) return ;int m = (l + r ) >> 1;build (lson );build (rson ); }void update (int p,int l,int r,int rt ) {if (l == r ) {sum [rt ] ++;return ;}int m = (l + r ) >> 1; if (p <= m ) update (p , lson );26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 5556 57 58else update (p , rson );PushUP (rt );} int query (int L,int R,int l,int r,int rt ) {if (L <= l && r <= R ) {return sum [rt ];}int m = (l + r ) >> 1;int ret = 0;if (L <= m ) ret += query (L , R , lson );if (R > m ) ret += query (L , R , rson );return ret ; }int x [maxn ];int main () {int n ;while (~scanf ("%d",&n )) {build (0 , n - 1 , 1);int sum = 0;for (int i = 0 ; i < n ; i ++) {scanf ("%d",&x [i ]);sum += query (x [i ] , n - 1 , 0 , n - 1 , 1);update (x [i ] , 0 , n - 1 , 1);}int ret = sum ;for (int i = 0 ; i < n ; i ++) {sum += n - x [i ] - x [i ] - 1;ret = min (ret , sum );} printf ("%d \n ",ret );}return 0; } o hdu2795 Billboard题意:h*w 的木板,放进一些1*L 的物品,求每次放空间能容纳且最上边的位子思路:每次找到最大值的位子,然后减去L线段树功能:query:区间求最大值的位子(直接把update 的操作在query 里做了)?View Code CPP1 2 3 4 5 6 #include <cstdio>#include <algorithm>using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 17 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 3839 40 41 42 const int maxn = 222222;int h , w , n ;int MAX [maxn <<2];void PushUP (int rt ) {MAX [rt ] = max (MAX [rt <<1] , MAX [rt <<1|1]);}void build (int l,int r,int rt ) {MAX [rt ] = w ;if (l == r ) return ;int m = (l + r ) >> 1;build (lson );build (rson ); }int query (int x,int l,int r,int rt ) {if (l == r ) {MAX [rt ] -= x ;return l ;}int m = (l + r ) >> 1;int ret = (MAX [rt <<1] >= x ) ? query (x , lson ) : query (x , rson );PushUP (rt );return ret ; }int main () {while (~scanf ("%d%d%d",&h,&w,&n )) {if (h > n ) h = n ;build (1 , h , 1);while (n --) {int x ;scanf ("%d",&x );if (MAX [1] < x ) puts ("-1"); else printf ("%d \n ",query (x , 1 , h , 1));}}return 0; } ∙ 练习:o poj2828 Buy Ticketso poj2886 Who Gets the Most Candies?∙ 成段更新(通常这对初学者来说是一道坎),需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or 询问到的时候 o hdu1698 Just a Hook题意:O(-1)思路:O(-1)线段树功能:update:成段替换 (由于只query 一次总区间,所以可以直接输出1结点的信息)?View Code CPP1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <cstdio>#include <algorithm>using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 111111;int h , w , n ;int col [maxn <<2];int sum [maxn <<2];void PushUp (int rt ) {sum [rt ] = sum [rt <<1] + sum [rt <<1|1];}void PushDown (int rt,int m ) {if (col [rt ]) {col [rt <<1] = col [rt <<1|1] = col [rt ];sum [rt <<1] = (m - (m >> 1)) * col [rt ];sum [rt <<1|1] = (m >> 1) * col [rt ];col [rt ] = 0;} }void build (int l,int r,int rt ) {col [rt ] = 0;sum [rt ] = 1;if (l == r ) return ;int m = (l + r ) >> 1;build (lson );build (rson );PushUp (rt ); }void update (int L,int R,int c,int l,int r,int rt ) {if (L <= l && r <= R ) {col [rt ] = c ;sum [rt ] = c * (r - l + 1);return ;}PushDown (rt , r - l + 1);int m = (l + r ) >> 1;if (L <= m ) update (L , R , c , lson );if (R > m ) update (L , R , c , rson ); PushUp (rt );42 43 44 45 46 47 48 49 50 51 52 53 5455 56 57 }int main () {int T , n , m ;scanf ("%d",&T );for (int cas = 1 ; cas <= T ; cas ++) {scanf ("%d%d",&n,&m );build (1 , n , 1);while (m --) {int a , b , c ;scanf ("%d%d%d",&a,&b,&c );update (a , b , c , 1 , n , 1);} printf ("Case %d: The total value of the hook is %d.\n ",cas , sum [1]);}return 0; } o poj3468 A Simple Problem with Integers题意:O(-1)思路:O(-1)线段树功能:update:成段增减 query:区间求和?View Code CPP1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <cstdio>#include <algorithm>using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1#define LL long longconst int maxn = 111111;LL add [maxn <<2];LL sum [maxn <<2];void PushUp (int rt ) {sum [rt ] = sum [rt <<1] + sum [rt <<1|1];}void PushDown (int rt,int m ) {if (add [rt ]) {add [rt <<1] += add [rt ];add [rt <<1|1] += add [rt ];sum [rt <<1] += add [rt ] * (m - (m >> 1));sum [rt <<1|1] += add [rt ] * (m >> 1);add [rt ] = 0;} }23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 void build (int l,int r,int rt ) {add [rt ] = 0;if (l == r ) {scanf ("%lld",&sum [rt ]);return ;}int m = (l + r ) >> 1;build (lson );build (rson );PushUp (rt ); }void update (int L,int R,int c,int l,int r,int rt ) {if (L <= l && r <= R ) {add [rt ] += c ;sum [rt ] += (LL )c * (r - l + 1);return ;}PushDown (rt , r - l + 1);int m = (l + r ) >> 1;if (L <= m ) update (L , R , c , lson );if (m < R ) update (L , R , c , rson );PushUp (rt ); }LL query (int L,int R,int l,int r,int rt ) {if (L <= l && r <= R ) {return sum [rt ];}PushDown (rt , r - l + 1);int m = (l + r ) >> 1;LL ret = 0;if (L <= m ) ret += query (L , R , lson );if (m < R ) ret += query (L , R , rson );return ret ; }int main () {int N , Q ;scanf ("%d%d",&N,&Q );build (1 , N , 1);while (Q --) {char op [2];int a , b , c ;scanf ("%s",op );if (op [0] == 'Q') { scanf ("%d%d",&a,&b );6768 69 70 71 72 73 74 printf ("%lld \n ",query (a , b , 1 , N , 1));} else {scanf ("%d%d%d",&a,&b,&c );update (a , b , c , 1 , N , 1);}}return 0; } o poj2528 Mayor’s posters题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报思路:这题数据范围很大,直接搞超时+超内存,需要离散化:离散化简单的来说就是只取我们需要的值来用,比如说区间[1000,2000],[1990,2012] 我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,所以我只需要1000,1990,2000,2012就够了,将其分别映射到0,1,2,3,在于复杂度就大大的降下来了 所以离散化要保存所有需要用到的值,排序后,分别映射到1~n,这样复杂度就会小很多很多 而这题的难点在于每个数字其实表示的是一个单位长度(并且一个点),这样普通的离散化会造成许多错误(包括我以前的代码,poj 这题数据奇弱)给出下面两个简单的例子应该能体现普通离散化的缺陷:1-10 1-4 5-101-10 1-4 6-10为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,2,6,10]如果相邻数字间距大于1的话,在其中加上任意一个数字,比如加成[1,2,3,6,7,10],然后再做线段树就好了.线段树功能:update:成段替换 query:简单hash?View Code CPP1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <cstdio>#include <cstring>#include <algorithm>using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 11111;bool hash [maxn ];int li [maxn ] , ri [maxn ];int X [maxn *3];int col [maxn <<4];int cnt ;void PushDown (int rt ) {if (col [rt ] != -1) {col [rt <<1] = col [rt <<1|1] = col [rt ];col [rt ] = -1;}20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 }void update (int L,int R,int c,int l,int r,int rt ) {if (L <= l && r <= R ) {col [rt ] = c ;return ;}PushDown (rt );int m = (l + r ) >> 1;if (L <= m ) update (L , R , c , lson );if (m < R ) update (L , R , c , rson ); }void query (int l,int r,int rt ) {if (col [rt ] != -1) {if (!hash [col [rt ]]) cnt ++;hash [ col [rt ] ] = true ;return ;}if (l == r ) return ;int m = (l + r ) >> 1;query (lson );query (rson ); }int Bin (int key,int n,int X []) {int l = 0 , r = n - 1;while (l <= r ) {int m = (l + r ) >> 1;if (X [m ] == key ) return m ;if (X [m ] < key ) l = m + 1;else r = m - 1;}return -1; }int main () {int T , n ;scanf ("%d",&T );while (T --) {scanf ("%d",&n );int nn = 0;for (int i = 0 ; i < n ; i ++) {scanf ("%d%d",&li [i ] , &ri [i ]);X [nn ++] = li [i ];X [nn ++] = ri [i ];} sort (X , X + nn );64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 8182 83 84int m = 1;for (int i = 1 ; i < nn ; i ++) {if (X [i ] != X [i -1]) X [m ++] = X [i ];}for (int i = m - 1 ; i > 0 ; i --) {if (X [i ] != X [i -1] + 1) X [m ++] = X [i -1] + 1;}sort (X , X + m );memset (col , -1 , sizeof (col ));for (int i = 0 ; i < n ; i ++) {int l = Bin (li [i ] , m , X );int r = Bin (ri [i ] , m , X );update (l , r , i , 0 , m , 1);}cnt = 0;memset (hash , false , sizeof (hash ));query (0 , m , 1); printf ("%d \n ",cnt );}return 0; } o poj3225 Help with Intervals题意:区间操作,交,并,补等思路:我们一个一个操作来分析:(用0和1表示是否包含区间,-1表示该区间内既有包含又有不包含)U:把区间[l,r]覆盖成1I:把[-∞,l)(r,∞]覆盖成0D:把区间[l,r]覆盖成0C:把[-∞,l)(r,∞]覆盖成0 , 且[l,r]区间0/1互换S:[l,r]区间0/1互换成段覆盖的操作很简单,比较特殊的就是区间0/1互换这个操作,我们可以称之为异或操作 很明显我们可以知道这个性质:当一个区间被覆盖后,不管之前有没有异或标记都没有意义了所以当一个节点得到覆盖标记时把异或标记清空而当一个节点得到异或标记的时候,先判断覆盖标记,如果是0或1,直接改变一下覆盖标记,不然的话改变异或标记开区间闭区间只要数字乘以2就可以处理(偶数表示端点,奇数表示两端点间的区间)线段树功能:update:成段替换,区间异或 query:简单hash?View Code CPP1 2 3 4 #include <cstdio>#include <cstring>#include <cctype>#include <algorithm>5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 131072;bool hash [maxn ];int cover [maxn <<2];int XOR [maxn <<2];void FXOR (int rt ) {if (cover [rt ] != -1) cover [rt ] ^= 1;else XOR [rt ] ^= 1; }void PushDown (int rt ) {if (cover [rt ] != -1) {cover [rt <<1] = cover [rt <<1|1] = cover [rt ];XOR [rt <<1] = XOR [rt <<1|1] = 0;cover [rt ] = -1;}if (XOR [rt ]) {FXOR (rt <<1);FXOR (rt <<1|1);XOR [rt ] = 0;} }void update (char op,int L,int R,int l,int r,int rt ) {if (L <= l && r <= R ) {if (op == 'U') {cover [rt ] = 1;XOR [rt ] = 0;} else if (op == 'D') {cover [rt ] = 0;XOR [rt ] = 0;} else if (op == 'C' || op == 'S') {FXOR (rt );}return ;}PushDown (rt );int m = (l + r ) >> 1;if (L <= m ) update (op , L , R , lson );else if (op == 'I' || op == 'C') {XOR [rt <<1] = cover [rt <<1] = 0;} if (m < R ) update (op , L , R , rson );4950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 else if(op =='I'|| op =='C'){XOR[rt<<1|1]= cover[rt<<1|1]=0; }}void query(int l,int r,int rt){if(cover[rt]==1){for(int it = l ; it <= r ; it ++){hash[it]=true;}return;}else if(cover[rt]==0)return;if(l == r)return;PushDown(rt);int m =(l + r)>>1;query(lson);query(rson);}int main(){cover[1]= XOR[1]=0;char op , l , r;int a , b;while( ~scanf("%c %c%d,%d%c\n",&op , &l , &a , &b , &r)){a <<=1 ,b <<=1;if(l =='(') a ++;if(r ==')') b --;if(a > b){if(op =='C'|| op =='I'){cover[1]= XOR[1]=0;}}else update(op , a , b , 0 , maxn , 1);}query(0 , maxn , 1);bool flag =false;int s =-1 , e;for(int i =0; i <= maxn ; i ++){if(hash[i]){if(s ==-1) s = i;e = i;}else{if(s !=-1){if(flag)printf(" ");flag =true;printf("%c%d,%d%c",s&1?'(':'[' , s>>1 , (e+1)>>1 , e&1?')':']');93 94 95 96 97 98 99s = -1;}}}if (!flag ) printf ("empty set");puts ("");return 0;} ∙ 练习: o poj1436 Horizontally Visible Segmentso poj2991 Craneo Another LCISo Bracket Sequence∙ 区间合并这类题目会询问区间中满足条件的连续最长区间,所以PushUp 的时候需要对左右儿子的区间进行合并o poj3667 Hotel题意:1 a:询问是不是有连续长度为a 的空房间,有的话住进最左边2 a b:将[a,a+b-1]的房间清空思路:记录区间中最长的空房间线段树操作:update:区间替换 query:询问满足条件的最左断点?View Code CPP1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <cstdio>#include <cstring>#include <cctype>#include <algorithm>using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 55555;int lsum [maxn <<2] , rsum [maxn <<2] , msum [maxn <<2];int cover [maxn <<2];void PushDown (int rt,int m ) {if (cover [rt ] != -1) {cover [rt <<1] = cover [rt <<1|1] = cover [rt ];msum [rt <<1] = lsum [rt <<1] = rsum [rt <<1] = cover [rt ] ? 0 : m -(m >> 1);msum [rt <<1|1] = lsum [rt <<1|1] = rsum [rt <<1|1] = cover [rt ] ? 0 : (m >> 1);cover [rt ] = -1; }22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 }void PushUp (int rt,int m ) {lsum [rt ] = lsum [rt <<1];rsum [rt ] = rsum [rt <<1|1];if (lsum [rt ] == m - (m >> 1)) lsum [rt ] += lsum [rt <<1|1];if (rsum [rt ] == (m >> 1)) rsum [rt ] += rsum [rt <<1];msum [rt ] = max (lsum [rt <<1|1] + rsum [rt <<1] , max (msum [rt <<1] , msum [rt <<1|1]));}void build (int l,int r,int rt ) {msum [rt ] = lsum [rt ] = rsum [rt ] = r - l + 1;cover [rt ] = -1;if (l == r ) return ;int m = (l + r ) >> 1;build (lson );build (rson ); }void update (int L,int R,int c,int l,int r,int rt ) {if (L <= l && r <= R ) {msum [rt ] = lsum [rt ] = rsum [rt ] = c ? 0 : r - l + 1;cover [rt ] = c ;return ;}PushDown (rt , r - l + 1);int m = (l + r ) >> 1;if (L <= m ) update (L , R , c , lson );if (m < R ) update (L , R , c , rson );PushUp (rt , r - l + 1); }int query (int w,int l,int r,int rt ) {if (l == r ) return l ;PushDown (rt , r - l + 1);int m = (l + r ) >> 1;if (msum [rt <<1] >= w ) return query (w , lson );else if (rsum [rt <<1] + lsum [rt <<1|1] >= w ) return m - rsum [rt <<1] + 1;return query (w , rson ); }int main () {int n , m ;scanf ("%d%d",&n,&m );build (1 , n , 1);while (m --) {int op , a , b ; scanf ("%d",&op );66 67 68 69 70 7172 73 74 75 76 77if (op == 1) {scanf ("%d",&a );if (msum [1] < a ) puts ("0");else {int p = query (a , 1 , n , 1); printf ("%d \n ",p );update (p , p + a - 1 , 1 , 1 , n , 1);}} else {scanf ("%d%d",&a,&b );update (a , a + b - 1 , 0 , 1 , n , 1);}}return 0; } ∙ 练习:o hdu3308 LCISo hdu3397 Sequence operationo hdu2871 Memory Controlo hdu1540 Tunnel Warfareo CF46-D Parking Lot∙ 扫描线这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去 最典型的就是矩形面积并,周长并等题o hdu1542 Atlantis题意:矩形面积并思路:浮点数先要离散化;然后把矩形分成两条边,上边和下边,对横轴建树,然后从下到上扫描上去,用cnt 表示该区间下边比上边多几个线段树操作:update:区间增减 query:直接取根节点的值?View Code CPP1 2 3 4 5 6 7 8 9 10 11 12 13 #include <cstdio>#include <cstring>#include <cctype>#include <algorithm>using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 2222;int cnt [maxn << 2];double sum [maxn << 2];double X [maxn ];struct Seg {1415161718192021222324252627282930313233343536373839404142434445464748495051525354555657 double h , l , r;int s;Seg(){}Seg(double a,double b,double c,int d): l(a) , r(b) , h(c) , s(d){} bool operator <(const Seg &cmp)const{return h < cmp.h;}}ss[maxn];void PushUp(int rt,int l,int r){if(cnt[rt]) sum[rt]= X[r+1]- X[l];else if(l == r) sum[rt]=0;else sum[rt]= sum[rt<<1]+ sum[rt<<1|1];}void update(int L,int R,int c,int l,int r,int rt){if(L <= l && r <= R){cnt[rt]+= c;PushUp(rt , l , r);return;}int m =(l + r)>>1;if(L <= m) update(L , R , c , lson);if(m < R) update(L , R , c , rson);PushUp(rt , l , r);}int Bin(double key,int n,double X[]){int l =0 , r = n -1;while(l <= r){int m =(l + r)>>1;if(X[m]== key)return m;if(X[m]< key) l = m +1;else r = m -1;}return-1;}int main(){int n , cas =1;while(~scanf("%d",&n)&& n){int m =0;while(n --){double a , b , c , d;scanf("%lf%lf%lf%lf",&a,&b,&c,&d);X[m]= a;ss[m++]= Seg(a , c , b , 1);X[m]= c;58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 7576 77 78ss [m ++] = Seg (a , c , d , -1);}sort (X , X + m );sort (ss , ss + m );int k = 1;for (int i = 1 ; i < m ; i ++) {if (X [i ] != X [i -1]) X [k ++] = X [i ];}memset (cnt , 0 , sizeof (cnt ));memset (sum , 0 , sizeof (sum ));double ret = 0;for (int i = 0 ; i < m - 1 ; i ++) {int l = Bin (ss [i ].l , k , X );int r = Bin (ss [i ].r , k , X ) - 1;if (l <= r ) update (l , r , ss [i ].s , 0 , k - 1, 1);ret += sum [1] * (ss [i +1].h - ss [i ].h );} printf ("Test case #%d \n Total explored area: %.2lf \n\n ",cas ++ , ret );}return 0; } o hdu1828 Picture题意:矩形周长并思路:与面积不同的地方是还要记录竖的边有几个(numseg 记录),并且当边界重合的时候需要合并(用lbd 和rbd 表示边界来辅助)线段树操作:update:区间增减 query:直接取根节点的值?View Code CPP1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <cstdio>#include <cstring>#include <cctype>#include <algorithm>using namespace std ;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 22222;struct Seg {int l , r , h , s ;Seg () {}Seg (int a,int b,int c,int d ):l (a ) , r (b ) , h (c ) , s (d ) {}bool operator < (const Seg &cmp ) const {if (h == cmp.h ) return s > cmp.s ; return h < cmp.h ;1718192021222324252627282930313233343536373839404142434445464748495051525354555657585960 }}ss[maxn];bool lbd[maxn<<2] , rbd[maxn<<2];int numseg[maxn<<2];int cnt[maxn<<2];int len[maxn<<2];void PushUP(int rt,int l,int r){if(cnt[rt]){lbd[rt]= rbd[rt]=1;len[rt]= r - l +1;numseg[rt]=2;}else if(l == r){len[rt]= numseg[rt]= lbd[rt]= rbd[rt]=0;}else{lbd[rt]= lbd[rt<<1];rbd[rt]= rbd[rt<<1|1];len[rt]= len[rt<<1]+ len[rt<<1|1];numseg[rt]= numseg[rt<<1]+ numseg[rt<<1|1];if(lbd[rt<<1|1]&& rbd[rt<<1]) numseg[rt]-=2;//两条线重合}}void update(int L,int R,int c,int l,int r,int rt){if(L <= l && r <= R){cnt[rt]+= c;PushUP(rt , l , r);return;}int m =(l + r)>>1;if(L <= m) update(L , R , c , lson);if(m < R) update(L , R , c , rson);PushUP(rt , l , r);}int main(){int n;while(~scanf("%d",&n)){int m =0;int lbd =10000, rbd =-10000;for(int i =0; i < n ; i ++){int a , b , c , d;scanf("%d%d%d%d",&a,&b,&c,&d);lbd = min(lbd , a);rbd = max(rbd , c);ss[m++]= Seg(a , c , b , 1);ss[m++]= Seg(a , c , d , -1);~ 21 ~ 61 62 63 64 65 66 67 68 69 70 7172 73}sort (ss , ss + m );int ret = 0 , last = 0;for (int i = 0 ; i < m ; i ++) {if (ss [i ].l < ss [i ].r ) update (ss [i ].l , ss [i ].r - 1 , ss [i ].s , lbd , rbd - 1 , 1);ret += numseg [1] * (ss [i +1].h - ss [i ].h );ret += abs (len [1] - last );last = len [1];} printf ("%d \n ",ret );}return 0; }练习:。