zoukankan      html  css  js  c++  java
  • 线段树学习笔记

    (Part 1:) 线段树简介

    线段树这个东西,学之前感觉很难很冷门,学之后感觉很难很热门。
    首先我们先理解一下“线段树”这个词语。顾名思义,根据我们语文老师提供的拆词法,拆成“线段”+“树”两个部分。易得,这是一种树形数据结构,其节点基于线段操作。
    我们使用一个插图来了解一下具体的一颗线段树:
    seg1.png
    显然可以看出,线段树的每个节点是一个线段。而在这个线段上,我们通常维护一些跟线段有关的值比如区间和或者最大值什么的。

    (1.Build Tree) 千里之行,始于建树

    这可不是什么好笑的事情,每年有那么多的(OIer)因为忘记建树而.......(缅怀同胞)
    如何建树呢?在建树的时候,我们通常把一些很基本的信息添加到树上去,一般是区间左右端点和区间和。
    如果当前节点是叶子节点,即(t[u].l==t[u].r),那么我们就把属于这一个长度为(1)的区间的值给到(t[u].sum),一般来说这个值题目会给出。
    如果不是叶子节点,那么我们先求出一个(mid),此处有二分的思想,向左右分别递归,形如:
    BuildTree(u*2,l,mid);
    BuildTree(u*2+1,mid+1,r);
    在递归完之后别忘了把左右儿子的值加给父节点:
    t[u].sum=t[u*2].sum+t[u*2+1].sum;

    (2.) 普天之下,莫非王土;率土之滨,区间修改

    区间修改是个重点。
    我们想一下,如果把区间修改改成(r-l+1)次单点修改,这个时间复杂度是我们不能接受的。
    区间修改的主要思想是#lazy-tag#,即,如果想要修改([1,6])这个区间如上图,那么我们并不是立即把修改操作向下传递到叶子节点,而是现在(1)号节点这里记录一个(tag)。具体的,如果我想([1,6]+=5),那么我先(t[1].tag+=5),其他地方先不改动。
    在修改完(tag)之后,需要立刻下传递一次,注意,是一次,所以时间复杂度是(Theta (1))。具体的操作如下:

    if(t[p].add)
    {
      	t[p*2].add+=t[p].add;
      	t[p*2+1].add+=t[p].add;
      	t[p*2].sum+=(t[p*2].r-t[p*2].l+1)*t[p].add;
      	t[p*2+1].sum+=(t[p*2+1].r-t[p*2+1].l+1)*t[p].add;
      	t[p].add=0;
    }
    

    这样做是为了之后从儿子节点更新(t[u].sum),具体操作见“千里之行”章节。
    之后也是一样,算出(mid)之后向左右递归即可。

    void pd(int p)
    {
    	if(t[p].add)
    	{
    		t[p*2].add+=t[p].add;
    		t[p*2+1].add+=t[p].add;
    		t[p*2].sum+=(t[p*2].r-t[p*2].l+1)*t[p].add;
    		t[p*2+1].sum+=(t[p*2+1].r-t[p*2+1].l+1)*t[p].add;
    		t[p].add=0;
    	}
    }
    void modify(int p,int l, int r,int d)
    {
    	if(l<=t[p].l&&t[p].r<=r){t[p].add+=d;t[p].sum+=(t[p].r-t[p].l+1)*d;return;}
    	pd(p);
    	int mid=(t[p].l+t[p].r)/2;
    	if(l<=mid)modify(p*2,l,r,d);
    	if(r>mid)modify(p*2+1,l,r,d);
    	t[p].sum=t[p*2].sum+t[p*2+1].sum;
    }
    

    (3.) 明察秋毫,区间查询

    区间查询其实跟区间修改特别像,详见“普天之下,莫非王土”章节。
    直接查找,向两侧递归即可。

    ll query(int p,int l,int r)
    {
    	if(l<=t[p].l&&t[p].r<=r)return t[p].sum;
    	pd(p);
    	int mid=(t[p].l+t[p].r)/2;
    	ll ans=0;
    	if(l<=mid)ans+=query(p*2,l,r);
    	if(r>mid)ans+=query(p*2+1,l,r);
    	return ans;
    }
    

    线段树的基本操作就讲到这里,看官们别忘了点赞收藏哦!

  • 相关阅读:
    关于联想笔记本ThinkPad E470 没有外音 插耳机却有声音的解决办法
    Win10无法启动软件提示MSVCP110.dll丢失
    POJ-3984 迷宫问题(BFS找最短路径并保存)
    转圈游戏(简单的快速幂)
    统计一个整数的二进制中1的个数(暴力)
    手写哈希(实现简单的加数、查询)
    CodeForces
    L2-2 社交集群 (25 分)(一个写挫的并查集)
    7-4 交换二叉树中每个结点的左孩子和右孩子 (20 分)
    7-3 堆中的路径 (25 分)
  • 原文地址:https://www.cnblogs.com/juruo-wsy/p/14728124.html
Copyright © 2011-2022 走看看