线段树心得

核心知识点:

(i)如何构造一颗线段树:

void build(int rt,int L,int R)

{

   if(L==R)

    scanf("%d",&Max[rt]);

   else {  

       int M=(L+R)/2;

      build(rt*2,L,M);

       build(rt*2+1,M+1,R);

       Max[rt]=max(Max[rt*2]+Max[rt*2+1]);

      //把孩子的节点信息更新到叶子节点

     }

} //相当于一个后序遍历。

首先应当明确,整个过程是由后到前的,也正如学长所给的代码,最后一句画龙点睛的讲到是相当于一个后续遍历。怎么呢?当每个部分不是一个元节点的时候,开始的那个if是没有调用的,直接是执行下面的语句。也就是说,一段长度的线段它内部的最大值是没有确定下来的。到最后当每个孩子节点的值确定后才会通过递归调用的方式返回,慢慢的填满整个树的结构。

询问:即通过遍历每个在查询范围的小区间,从中选出最大值,ql,qr代表查询的区间

int query(int r,int L,int R)

{

int ans=-1;

int M=(L+R)/2;

if(ql<=L&&R<=qr)

return Max[r]; //区间要全部在查询的区间上(这个是查询的区间比已经有的区间还要大,直接返回已有的最大值)

if(qr<=M) return query(r*2,L,M); //查询的区间在根节点的左孩子上,进入左孩子查询

else if(ql>M) return query(r*2+1,M+1,R); //查询的区间在根节点的右孩子上,进入右孩子查询

else { ans=max(query(r*2,L,M),query(r*2+1,M+1,R));

}

//若是查询区间在左孩子的区间和右孩子的区间都有,则进入该区间的左右孩子查询,并返还其最大值

//其实你仔细看代码就会发现,真正返回有效值的操作是第一步,但是讨论是必须要分为四个步骤进行的

更新:即先更新每个叶子节点,区间长度为1的节点。叶子节点更新好后,就把节点信息往上更新,直到根节点。

void update(int r,int L,int R)

{ int M=(L+R)/2;

if(L==R) Q[r]=v; ///直到范围长度为1的时候即找到要更新的叶子节点

else {

if(p<=M) update(r*2,L,M);

else update(r*2+1,M+1,R); ///不断缩小搜索范围,与2分有点像。

Q[r]=max(Q[r*2],Q[r*2+1]); ///叶子节点更新后,开始向上更新。

}

}///更新线段树

//在这里,r的值相当于目前处理的值的下标,注意,他们所有的值都是通过数组进行保存的,整颗二叉树,注意,这个函数中有两处给Q[r]赋值的地方,这也就是直接赋值和递归赋值的两个端口

/////////////////////////////////////////////////////////

//下面还是参考上次的模版题——排兵布阵

#include <iostream>
#include <stdio.h>
#include <memory.h>
using namespace std;

int n, a[50005];
char sh[15];

int lowbit(int i) //树状数组最巧妙之处:i&(-i)
{
return i&(-i);
} //满足2^k<=t的最大的2^k,其中k为非负整数

void update(int i, int val) //更新函数
{
while(i <= n)
{
a[i] += val;
i += lowbit(i);
}
}

int sum(int i) //求和函数
{
int sum = 0;
while(i > 0)
{
sum += a[i];
i -= lowbit(i);
}
return sum;
}

int main()
{
int i, val, t, x, y, zz = 1;
scanf("%d", &t);
while(t--)
{
memset(a, 0, sizeof(a));
scanf("%d", &n);
for(i = 1; i <= n; i++)
{
scanf("%d", &val);
update(i, val); //在实际的运用中是没有创建这个操作的,直接是使用更新这个方式实现的
}
printf("Case %d:\n", zz++);
while(scanf("%s", sh))
{
if(sh[0] == ‘E‘) break;
scanf("%d %d", &x, &y);
if(sh[0] == ‘A‘) update(x, y);
else if(sh[0] == ‘S‘) update(x, -y);
else printf("%d\n", sum(y)-sum(x-1)); //两段区间和相减
}
}

return 0;
}

线段树心得

时间: 2024-12-16 14:37:28

线段树心得的相关文章

poj--3667 Hotel(线段树+区间合并)

Description The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and enjoy a vacation on the sunny shores of Lake Superior. Bessie, ever the competent travel agent, has named the Bullmoose Hotel on famed Cumberland Stree

【BZOJ3110】【Zjoi2013】K大数查询 树套树 权值线段树套区间线段树

#include <stdio.h> int main() { puts("转载请注明出处谢谢"); puts("http://blog.csdn.net/vmurder/article/details/43020009"); } 题解: 外层权值线段树,内层区间线段树可解. 权值都是1~n,就不用离散化了. 我写了标记永久化. 其它心得神马的: 天生对树形数据结构无爱. 第一次写树套树,终于知道是怎么回事了. (只针对本题) 就是外层每个点都表示了一段

Balanced Lineup(线段树的简单了解)

个人心得:线段树就是将一段序列拆分为一个个单独的节点,不过每俩个节点又可以联系在一起,所以就能很好的结合,比如这一题, 每次插入的时候都将这一段区间的最大最小值更新,就能大大减少时间. 这个线段树建立是以数组的,根节点为0,后面每次都是父节点*2+1/2. 这题简单的教会了我如何创建线段树,以及一些简单的线段树操作,还要继续加深. For the daily milking, Farmer John's N cows (1 ≤ N ≤ 50,000) always line up in the

Buy Tickets(线段树单点更新,逆向思维)

题目大意:有n个的排队,每一个人都有一个val来对应,每一个后来人都会插入当前队伍的某一个位置pos.要求把队伍最后的状态输出. 个人心得:哈哈,用链表写了下,果不其然超时了,后面转念一想要用静态数组思维, 还是炸了.大牛们很给力,逆向一转,真是服气. 一想是呀,转过来的话那么此时的人必然可以得到他的位置,此时更新长度,后面的人在此时的队列中依旧可以得到他想要的位置. 就算思路知道了,怎么实现呢,大神果然不愧是大神,线段树sum表示总长度,节点表示是否存在被占据,然后更新就可以了. 真的是佩服到

YSZOJ:#247. [福利]可持久化线段树 (最适合可持久化线段树入门)

题目链接:https://syzoj.com/problem/247 解题心得: 可持久化线段树其实就是一个线段树功能的加强版,加强在哪里呢?那就是如果一颗普通的线段树多次修改之后还能知道最开始的线段树长什么样子吗?肯定不能,如果就要问你这棵线段树在某一时刻是什么样子那能咋办.最直接的思维就是创建n棵线段树,将每一个时刻都记录下来.但是想一下时间复杂度和空间复杂度都是很大的. 可持久化线段树就是解决这个问题的.仔细想一想如果只修改线段树上一个叶子结点,那么对于线段树来说有改变的节点有多少个,lo

线段树summer_work

写了几道题,对线段树的概念有了一定的认识,这里总结下我写的习惯,方便以后完善及复习. 线段树所用的函数: pushup():向上更新父节点 build(): 与普通建树方式相同,最后要pushup()(向上更新(父节点的值) update():判断当前区间与给定区间的关系:若是点:找到点更新即可,若是区间:更新到rt即可,只有用到rt子节点,才向下更新.递归向下结束后,在向上的过程中更新父节点. query(): 判断当前rt与给定区间的关系,若用到lazy操作,查询时用到rt子节点则向下更新,

[poj2104]可持久化线段树入门题(主席树)

解题关键:离线求区间第k小,主席树的经典裸题: 对主席树的理解:主席树维护的是一段序列中某个数字出现的次数,所以需要预先离散化,最好使用vector的erase和unique函数,很方便:如果求整段序列的第k小,我们会想到离散化二分和线段树的做法, 而主席树只是保存了序列的前缀和,排序之后,对序列的前缀分别做线段树,具有差分的性质,因此可以求任意区间的第k小,如果主席树维护索引,只需要求出某个数字在主席树中的位置,即为sort之后v中的索引:若要求第k大,建树时反向排序即可 1 #include

【BZOJ4942】[Noi2017]整数 线段树+DFS(卡过)

[BZOJ4942][Noi2017]整数 题目描述去uoj 题解:如果只有加法,那么直接暴力即可...(因为1的数量最多nlogn个) 先考虑加法,比较显然的做法就是将A二进制分解成log位,然后依次更新这log位,如果最高位依然有进位,那么找到最高位后面的第一个0,将中间的所有1变成0,那个0变成1.这个显然要用到线段树,但是复杂度是nlog2n的,肯定过不去. 于是我在考场上yy了一下,这log位是连续的,我们每次都要花费log的时间去修改一个岂不是很浪费?我们可以先在线段树上找到这段区间

bzoj1798: [Ahoi2009]Seq 维护序列seq 线段树

题目传送门 这道题就是线段树 先传乘法标记再传加法 #include<cstdio> #include<cstring> #include<algorithm> #define LL long long using namespace std; const int M=400010; LL read(){ LL ans=0,f=1,c=getchar(); while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}