线段树(一种二叉搜索树)
树形结构的特点让它更方便查询搜索。
线段树方便与对区间查询,区间更新维护,这是因为树上的父亲节点表示了一段区间。叶子结点才是原始的元素。
如上图所示:
利用线段树解决问题的步骤如下(以模版题——给N个数,求任意区间合为例子):
对于这个算法个人认为必需理解:1.在建树时怎样将树全部遍历,将节点处理成区间和。2.在维护区间时利用lazy标记将数据添加到树中,并且如何在需要时将节点的lazy标记压入下面的节点。3.在查询区间时如何在需要时将节点的lazy标记压入下面的节点。
以上所述是算法的精髓所在,通则达。
详解如下
//预处理数据和主函数,方便后续阅读代码。p[]数组存放录入的数据,tr[N<<2]存放节点的值和lazy标记。
const int N=1e5+5;
int p[N];
struct node{
long long sum,lazy;
}tr[N<<2];
int main()
{
int n,m,i,k,a,b,q;
scanf("%d%d",&n,&m); //输入n个数字,m次处理
for(i=1;i<=n;i++){
scanf("%d",&p[i]);
}
build_tree(1,1,n);
while(m--){
scanf("%d",&q);
if(q==1){ //1表示区间加上k值
scanf("%d%d%d",&a,&b,&k);
add(1,1,n,a,b,k);
}
else{ //2表示 查询a到b的和
scanf("%d%d",&a,&b);
printf("%lld\n",search_tree(1,1,n,a,b));
}
}
return 0;
}
1.利用题目中的关系建树,利用DFS,递归遍历到树的每一个点,这里面主要涉及到递归的回溯,将所有点都遍历到;
代码如下:
void update(int root)
{
tr[root].sum=tr[root<<1].sum+tr[root<<1|1].sum; //将左右儿子节点上加到父亲节点。这就是区间的含义。
}
void build_tree(int root, int l, int r)
{
if(l==r){
tr[root].sum=p[l] ; //满足条件(遍历到了叶子结点)
return;
}
int mid=(l+r)>>1;
build_tree(root<<1,l,mid); //分流,左儿子;
build_tree(root<<1|1,mid+1,r); //分流,右儿子;
update(root); //传递给父亲节点
}
要是没学过dfs 理解会比较但一张图就可以让你豁然开朗!
2.下面是对区间的维护,对区间的维护就赤裸裸地体现了线段树的优势所在,每次查询一次就只需要查询到满足左右区间的父亲节点位置即可 ,有了lazy标记这玩意儿就大大缩减了所需时间。
代码如下:
void pushdown(int root, int l, int r) //pushdown函数使原节点的lazy值压入左右儿子
{
if(!tr[root].lazy) return; //一种剪枝吧,不错。
int mid=(l+r)>>1;
tr[root<<1].sum+=(tr[root].lazy*(mid-l+1)); //更新左儿子节点的和
tr[root<<1|1].sum+=(tr[root].lazy*(r-mid)); //更新右儿子节点的和
tr[root<<1].lazy+=tr[root].lazy; //更新左儿子节点lazy标记
tr[root<<1|1].lazy+=tr[root].lazy; //更新右儿子节点lazy标记
tr[root].lazy=0; //将父亲节点的lazy标记清零
}
void add(int root, int l, int r, int a, int b, int k)
{
if(l==a&&r==b){ //找到了目标节点区域
tr[root].lazy+=k; //更新
tr[root].sum+=k*(l-r+1); //更新
return ;
}
pushdown(root,l,r); // 每一次都要检查是否需要将父亲节点压入左右节点。
int mid=(l+r)>>1;
if(mid>=b){ // 需要到左区间
add(root<<1,l,mid,a,b,k);
}
else if(mid<a){ // 需要到右区间
add(root<<1|1,mid+1,r,a,b,k);
}
else{ // 左右区间都需要去找;
add(root<<1,l,mid,a,mid,k);
add(root<<1|1,mid+1,r,mid+1,b,k);
}
update(root); //同样需要将父亲节点更新;
}
3.查询区间和;(这个点和上面的区间维护相似,主要也是区间查询,不同之处就是要返回区间的值)
代码如下:
long long search_tree(int root, int l, int r, int a, int b)
{
if(l==a&&r==b){
return tr[root].sum;
}
pushdown(root,l,r); // 每次检查是否需要将父亲节点压入子节点;
int mid=(l+r)>>1;
if(mid>=b){
return search_tree(root<<1,l,mid,a,b); //返回查到的值,同样是递归实现;
}
else if(mid<a){
return search_tree(root<<1|1,mid+1,r,a,b);
}
else{
return search_tree(root<<1,l,mid,a,mid)+search_tree(root<<1|1,mid+1,r,mid+1,b);
}
}
学习这个算法时主要是要将递归实现理解透彻,将lazy标记这一方法理解透彻,将线段树这三个字理解透彻,区间区间区间?????No can no bibi !! …<~ - ~>…
耐心出奇迹
代码太长建议分块理解为妙:
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+5;
int p[N];
struct node{
long long sum,lazy;
}tr[N<<2];
void update(int root)
{
tr[root].sum=tr[root<<1].sum+tr[root<<1|1].sum;
}
void build_tree(int root, int l, int r)
{
if(l==r){
tr[root].sum=p[l];
return;
}
int mid=(l+r)>>1;
build_tree(root<<1,l,mid);
build_tree(root<<1|1,mid+1,r);
update(root);
}
void pushdown(int root, int l, int r)
{
if(!tr[root].lazy) return;
int mid=(l+r)>>1;
tr[root<<1].sum+=(tr[root].lazy*(mid-l+1));
tr[root<<1|1].sum+=(tr[root].lazy*(r-mid));
tr[root<<1].lazy+=tr[root].lazy;
tr[root<<1|1].lazy+=tr[root].lazy;
tr[root].lazy=0;
}
void add(int root, int l, int r, int a, int b, int k)
{
if(l==a&&r==b){
tr[root].lazy+=k;
tr[root].sum+=k*(l-r+1);
return ;
}
pushdown(root,l,r);
int mid=(l+r)>>1;
if(mid>=b){
add(root<<1,l,mid,a,b,k);
}
else if(mid<a){
add(root<<1|1,mid+1,r,a,b,k);
}
else{
add(root<<1,l,mid,a,mid,k);
add(root<<1|1,mid+1,r,mid+1,b,k);
}
update(root);
}
long long search_tree(int root, int l, int r, int a, int b)
{
if(l==a&&r==b){
return tr[root].sum;
}
pushdown(root,l,r);
int mid=(l+r)>>1;
if(mid>=b){
return search_tree(root<<1,l,mid,a,b);
}
else if(mid<a){
return search_tree(root<<1|1,mid+1,r,a,b);
}
else{
return search_tree(root<<1,l,mid,a,mid)+search_tree(root<<1|1,mid+1,r,mid+1,b);
}
}
int main()
{
int n,m,i,k,a,b,q;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%d",&p[i]);
}
build_tree(1,1,n);
while(m--){
scanf("%d",&q);
if(q==1){
scanf("%d%d%d",&a,&b,&k);
add(1,1,n,a,b,k);
}
else{
scanf("%d%d",&a,&b);
printf("%lld\n",search_tree(1,1,n,a,b));
}
}
return 0;
}
谢谢观看,喜欢就收藏呗!