集训笔记---线段树入门笔记
前几天在搬砖。听到了线段树这个名词,后来学长讲的时候也是昏昏欲睡,后来发现自己究竟错过了什么,后就开始百度其他大佬的题解,深深感觉到了淡淡的恶意,在一些关键位置总是缺斤短两让人听得迷迷糊糊,所以准备自己整理一下关于线段树的材料,自己写出一份入门级材料,好了,下面就要开始了。
首先要说明线段树可以用来解决什么样的问题,当然只是简单的概括,那就是区间问题,比如数组区间和,数组在某个区间的最大值这样的问题就可以用线段树来解决,那么,为什么不暴力呢,因为会TLE啊,在数据尺度很大时候线段树才是正解。
一道题目作为讲解http://acm.hdu.edu.cn/showproblem.php?pid=1754
目前解决的问题仅仅是单点修改,区间查找,后续博文会给出区间修改的博文
题目是在求区间最大值,线段树是二叉树的一种,在建树的过程中我们把原始数据,即所有学生的成绩存储在这棵树的叶子节点上,线段树之所以被叫做线段树是因为其中每个节点存储的都是一个区间上的信息,在这个题目中,每个节点上存储的都是各自区间上的最大值,那么我们从上往下走如下图所示
在这个问题中二叉树的编号从1开始,那么可以知道节点1存储1~10之间的最大值(我使用的是结构体存储错信息),他的左右孩子节点的存储范围其实是将节点1二分以后的结果,1~5, 6~10,节点2存储1~5间的最大值,节点3存储6~10间的最大值,以此类推,最终会到达节点1~1(最早到达的应该是3~3),这种节点存储的不就是人为需要输入的原始数据吗?在这个题目里面就是每个人的个人成绩,好的到这里,这棵树就已经建好了,下面看一下代码
struct edge
{
int st;
int en;
int big;
};
edge s[MAX_N];
void bulid_tree(int k, int l, int r)
{
if(s[k].st == s[k].en)//叶子结点将存储输入的原始信息
{
scanf("%d", &s[k].big);
return ;
}
int m = (l+r)/2;
bulid_tree((2*k), l, m);
bulid_tree((2*k)+1, (m+1), r);
s[k].big = max(s[2*k].big, s[(2*k)+1].big);//利用已经存储的数据更新其他节点
}
结构体中,st,en是在存储该节点表示的区间,big是区间的最大值,在这样的代码下面,会优先左孩子,然后是右孩子,
然后是根节点,需要注意的是在第一次调用函数的时候参数应为(1, 1, n),n是元素个数,可能会有人说宝宝看不懂,宝宝要换博客,其实,如果不自己写写递归过程,你换多少博客都是一样的结果,没有人能直接看穿递归过程,你必须一个阶段一个阶段的写,然后在return的时候回溯,你才能获取最终的结果,你才能看透这玩意儿到底在干啥。
树建好以后我们就可以躺好,然后利用其特殊的数据结构来轻松地解决问题
先说说修改吧,然后再说查询的问题,在这个题目中,出题人指出要修改ID为x的学生的成绩为y,那么其实不难明白,我们首先要修改的是叶子结点的信息,(就和我们输入的时候先输入叶子节点一样),然后与这个叶子结点关联的所有节点都要被更新,其实更新节点的过程和建树的过程有几分类似也是一个疯狂递归的过程
void update(int k, int x, int y)
{
//不难发现,对于叶子节点结构体里面的st或者en就是学生ID
if((s[k].st == s[k].en) && (s[k].st == x))
{
s[k].big = y;
return ;
}
int m = (l+r)/2;
if(x <= m)
{
update((2*k), l, m);
}
else
{
update((2*k)+1, (m+1), r);
}
s[k].big = max(s[2*k].big, s[(2*k)+1].big);
}
进行到这一步以后就可以去进行查询操作,怎么搞呢,我们需要找到一个区间,还以1~10这个区间为例,总共十个学生,需要找到3~5的最大值,那么请先看一看下面这个图,有助于理解
前面已经说过,节点存储的是某个范围的最大值,这个范围就是st~en,那么查询时我们需要访问的节点就只是我用方框圈起来的这两个,因为其他的节点对本次查询并不是必须要访问的,嘿嘿,弄明白这一点以后,其他的事情就好办了,
下面就是完整的代码了,下期再见
#include<cstdio>
#include<algorithm>
using namespace std;
struct edge
{
int st;
int en;
int big;
};
edge s[4000100];
int ans;
void bulid_tree(int l, int r, int k);
void update(int k, int x, int y);
void query(int k, int l, int r);
int main(void)
{
int n, m, i;
while(scanf("%d %d", &n, &m) != EOF)
{
bulid_tree(1, n, 1);
while(m--)
{
char we[5];
int x, y;
scanf("%s %d %d", we, &x, &y);
if(we[0] == 'Q')
{
ans = -1;
query(1, x, y);
printf("%d\n", ans);
}
else if(we[0] == 'U')
{
update(1, x, y);
}
}
}
}
void bulid_tree(int l, int r, int k)
{
s[k].st = l;
s[k].en = r;
if(l == r)
{
scanf("%d", &s[k].big);
return ;
}
int m = (l+r)/2;
bulid_tree(l, m, (2*k));
bulid_tree((m+1), r, ((2*k)+1));
s[k].big = max(s[2*k].big, s[(2*k)+1].big);
}
void update(int k, int x, int y)
{
if((s[k].st == s[k].en) && (s[k].st == x))
{
s[k].big = y;
return ;
}
int m = (s[k].st+s[k].en)/2;
if(x <= m)
{
update((2*k), x, y);
}
else
{
update(((2*k)+1), x, y);
}
s[k].big = max(s[2*k].big, s[(2*k)+1].big);
}
void query(int k, int l, int r)
{
if((s[k].st >= l) && (s[k].en <= r))
{
ans = max(ans, s[k].big);
return ;
}
int m = (s[k].st+s[k].en)/2;
//如果有左边的成分就向左边搜索
if(l <= m)
{
query((2*k), l, r);
}
//如果有向右边的成分就向右边搜索
if(r > m)
{
query((2*k)+1, l, r);
}
}