线段树一点理解+模板

本篇介绍线段树的区间查询和单点修改

线段树

  今天听了lfw讲线段树(lfw讲的真心好),也试着把今天的题目做了几道(嘤嘤嘤做不动啊啊好难),对于线段树的基础应用多少有了一点点体会。

   线段树顾名思义,是一种树形的数据结构,但不一样的是,这棵树的节点代表的是一个区间,也就是说,线段树很大程度上是在解决区间的问题,跟树状数组有点意思(但我觉得线段树更好理解额)。先看下面的图:

         相当于父节点保存了子节点的情况,比如,我们要求区间最大值,[1,1] [2,2]的父节点是[1,2],那么[1,2]这个节点保存的就是子树中的最大值,当我们要查询一段区间的最大值的时候,返回这个节点保存的值就可以了。由于是树形结构,我们查询的时候自然是只能从根节点开始,也就是从最大的区间,不断缩小,一直把区间缩小到我们要求的那个,那么,每次查询,我们经过的路径最多也就是logn,情况是比较理想的。

        那么,有的同学可能会发现,如果我们查询的区间并不是某个节点怎么办呢?比如我们要查询区间[4,8],显然没有一个节点是完整的4到8,但是我们可以发现,[4,8]可以划分为区间[4,5]和[6,8],如果我们要求 [4,8]的最大值,自然就是把这两个区间的最大值再取一个max啦。

        但是问题又来了:怎么把[4,8]划分成[4,5]和[6,8]呢?换句话说,我们怎么知道划分成了哪几个呢?下面我用文字模拟一遍这个寻找过程:

         从根节点的区间[1,10]开始,可以知道,根节点的左子树的根的区间右端点是10/2=5,右子树的根的区间左端点是10/2+1=6;显然,4在左子树中,而8是在右子树中,那我们就可以知道,我们要查询的这个区间肯定是分开一部分在左子树中,另一部分在右子树中,那么我们就分别在两棵子树中找,以此类推,直到找到。

下面是板子:

树的结构体

struct node
{
     int l,r;
     int mscore;
}tree[maxn<<2];

查询

可以结合代码理解:

int query(int k,int l,int r)//从编号k开始查询区间[l,r]的最大值
{
     //printf("mm\n");
     int mm;
     if(tree[k].l==l&&tree[k].r==r)//刚好找到了代表这个区间的节点
          return tree[k].mscore;
     int mid=(tree[k].l+tree[k].r)>>1;
     if(l>=mid+1)//要找的区间左端点都在右子树,那么只需到右子树中去找
          mm=query(k<<1|1,l,r);
     else if(r<=mid)//要找的区间右端点都在左子树,只需到左子树中找
          mm=query(k<<1,l,r);
     else//否则就是我们上面说的,到左右两边都去找,最后取两边的最大值
          mm=max(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
     return mm;
}

(树的数据结构,我们一般都采用递归的方法处理

总的来说,线段树可以很方便的实现对于区间的操作,比如求区间最大值啦、区间求和啦,都跟上面提到的思路差不多。

修改区间的值

         当然还有一个不容忽视的问题,当我们想要修改一个值的时候要怎么做呢?根据上面的图片我们可以看出,每个节点保存了一个区间的信息,当这个区间的某个值发生了改变,这个节点保存的信息就有可能发生变化,比如,如果节点保存的是一个区间的最大值,当我们把这个区间的某个值变大,那这个节点保存的信息就要改变。我们同样采用递归的方法来实现。

void change(int k,int d,int x)//从编号k开始把id为d的学生成绩改为x
{
     if(tree[k].l==d&&tree[k].r==d)//找到了代表这个点的叶子节点(叶子节点代表的区间只有一个值)
     {
          tree[k].mscore=x;
          return;
     }
     int mid=(tree[k].l+tree[k].r)>>1;
     if(d>=tree[k].l&&d<=mid)//在左子树
          change(k<<1,d,x);
     else              //在右子树
          change(k<<1|1,d,x);
     tree[k].mscore=max(tree[k<<1].mscore,tree[k<<1|1].mscore);
}

建树

忘记了最重要的一点:建树!即建出初始的树来支撑我们的修改和查询哇。思路也是递归

给出代码:

void build(int k,int l,int r)//以编号k为根节点建立从l到r的树
{
     tree[k].l=l;tree[k].r=r;
     if(l==r)
     {
          tree[k].mscore=a[l];
          return;
     }
     int mid=(l+r)>>1;
     build(k<<1,l,mid);build(k<<1|1,mid+1,r);
     tree[k].mscore=max(tree[k<<1].mscore,tree[k<<1|1].mscore);
     //printf("mm\n");
}

注意线段树的常数有点大,为了避免被有些题目卡常,*2、/2最好采用位运算

---------------------------------------------------------我只是个分割线哇------------------------------------------------------------------------------

下一篇是稍稍有点点麻烦的区间修改哇

呼呼

猜你喜欢

转载自blog.csdn.net/destiny1507/article/details/81673577
今日推荐