zoukankan      html  css  js  c++  java
  • 『数据结构』线段树板子

    线段树学习前提须知

    线段树的基础运用一般用于四个方面 单点修改 单点查询 区间修改 区间查询

    且代码易于理解,虽代码量很长 但是并这不妨碍我们学习这个算法

    算法内容

    基本概念

    1、线段树的形式类似于BST(二叉搜索树),在一些细节上区分于BST

    2、线段树的基本思想:分治

    3、每个节点都是以结构体的方式储存,结构体包含以下内容

    • 当前节点所包含的区间的左端点、右端点。

    • 这个区间所要维护的区间和(或其他 这里我们主要针对区间和)。

    4、线段树的父亲节点都是其儿子节点的结合,其中根节点的区间就是整个数列

    5、对于线段树:

    • 在树上的一个节点 (k),存在 (k) 的子节点分别为 (k*2)(k*2 + 1)

    • 对于这个节点 (k) ,它的左儿子的区间范围为 ([l, mid]) ,它的右儿子为 ([mid + 1, r])

    线段树代码与思路

    结构体

    const int N = ???;
    struct Node {
    	int l, r, w;
    } tree[N << 2];
    

    树记得开4倍空间

    线段树的基础操作有 5 个:建树、单点查询、单点修改、区间查询、区间修改

    建树

    思路:

    • 给每一个节点,赋予区间的左端点和右端点
    • 对于每一个叶子节点,我们赋予它们已知的单独的值
    • 利用线段树的性质,向上合并
    void build(int k, int l, int r) {
        tree[k].l = l; tree[k].r = r;
        if(l == r) {
            scanf("%d", &tree[k].w);
            return ;
        }
        
        int mid = (l + r) >> 1;
        build(k * 2, l, mid);
        build(k * 2 + 1, mid + 1, r);
        tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
    }
    

    单点修改(我们这里假设需要修改的点编号为 X,需要添加的值为 T)

    思路:

    • 若寻找的点所表示的区间包含我们要找的点的左区间和右区间 那么就往这个点走
    • 我们要修改的点一定在叶子节点上,查找这一部分需要消耗我们 (O(logn))
    • 修改了一个点就会导致上面包含当前区间的点都要变,于是修改这一步也是 (O(logn))
    //Now X and T has been entered;
    void change_point(int k) {
        if(tree[k].l == tree[k].r) {
            tree[k].w += t;
            return ;
        }
        
        int mid = (tree[k].l + tree[k].r) >> 1;
        if(mid >= x) change_point(k * 2);
        else change_point(k * 2 + 1);
    	tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
    }
    

    单点查询(这里设需要查询的点为 x)

    思路:

    • 和单点修改类似 若寻找的点所表示的区间包含我们要找的点的左区间和右区间 那么就往这个点走
    //Now x has been entered;
    int ans;
    void ask_point(int k) {
        if(tree[k].l == tree[k].r) {
            ans = tree[k].w;
            return ;
        }
        
        int mid = (tree[k].l + tree[k].r) >> 1;
        if(mid >= x) ask_point(k * 2);
        else ask_point(k * 2 + 1);
    }
    

    区间查询(设查询的范围为 [l, r])

    思路:

    • 和单点查询的思路类似,但这里换成了区间
    //Now l and r has been entered
    int ans;
    void ask_interval(int k, int l, int r) {
        if(tree[k].l >= l && tree[k].r <= r) {
            ans += tree[k].w;
            return ;
        }
        
        int mid = (tree[k].l + tree[k].r) >> 1;
        if(mid >= l) ask_interval(k * 2, l, r);
        if(mid < r) ask_interval(k * 2 + 1, l, r);
    }
    

    区间修改(※)

    思路:

    • 单纯的寻找和修改一定会超时,我们这里引进一个新的操作叫 Lazy(懒操作)
      • 懒操作就像你打MC服务器,没有加载的区块是不会有任何行为的,但实际上这个区块上却存在方块和动物,当你加载了这个区块之后,上面的一些行为才正常化,比如熔炉烧东西或者是树苗长大。
      • 懒操作实际就是一种差分的思想,把一些信息存储到一些点上,若这个点被遍历了,那么当前点的信息一定会被子节点所继承,其中子节点上若有其他信息,那这个信息是不会消失的
    //Now l,r,x has been entered
    struct Node {
        int l, r, w;
        int f;
    } tree[N >> 2];
    
    void Lazydown(int k) {
        tree[k * 2].f += tree[k].f;
        tree[k * 2].w += (tree[k * 2].r - tree[k * 2].l + 1) * tree[k].f;
        tree[k * 2 + 1].f += tree[k].f;
        tree[k * 2 + 1].w += (tree[k * 2 + 1].r - tree[k * 2 + 1].l + 1) * tree[k].f;
        tree[k].f = 0;
    }
    
    void change_interval(int k) {
        if(tree[k].l >= l && tree[k].r <= r) {
            tree[k].w += (tree[k].r - tree[k].l + 1) * x;
            tree[k].f += x;
            return ;
        }
        
        if(tree[k].f) Lazydown(k);
        int mid = (tree[k].l + tree[k].r) >> 1;
        if(mid >= l) change_interval(k * 2);
        if(mid < r) change_interval(k * 2 + 1);
        tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
    }
    

    有几个问题需要注意

    • 懒标记的结构体需要更改,需要添加一个当前节点所存储的信息
    • 这里我们发现修改变成了 tree[k].w += (tree[k].r - tree[k].l + 1) * x;,这里你可以画图进行验证
    • Lazydown中代表的意思就是,我们已经遍历了编号为 (k) 的点,那么已经遍历过了之后,肯定会将信息传递
    • Lazydown的修改操作原理和区间修改中的相同,若不理解,可以画图进行深究
    • 因为我们已经传递和遍历了,那么当前点的信息就会被清空(不然又会回档了(游戏说法

    加入了Lazydown之后,我们发现,只要遍历过的点都要进行懒操作,那么我们的所有代码都需要修改

    全代码如下

    //#define fre yes 信仰
    
    #include <cstdio>
    
    const int N = 200005;
    struct Node {
        int l, r, w;
        int f;
    } tree[N >> 2];
    
    void build(int k, int l, int r) {
        tree[k].l = l; tree[k].r = r;
        if(l == r) {
            scanf("%d", &tree[k].w);
            return ;
        }
        
        int mid = (l + r) >> 1;
        build(k * 2, l, mid);
        build(k * 2 + 1, mid + 1, r);
       	tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w
    }
    
    void Lazydown(int k) {
       	tree[k * 2].f += tree[k].f;
        tree[k * 2].w += (tree[k * 2].r - tree[k * 2].l + 1) * tree[k].f;
        tree[k * 2 + 1].f += tree[k].f;
        tree[k * 2 + 1].w += (tree[k * 2 + 1].r - tree[k * 2 + 1].l + 1) * tree[k].f;
        tree[k].f = 0;
    }
    
    void change_point(int k, int x) {
        if(tree[k].l == tree[k].r) {
            tree[k].w += x;
            return ;
        }
        
        if(tree[k].f) Lazydown(k); 
        int mid = (tree[k].l + tree[k].r) >> 1;
        if(mid >= l) change_point(k * 2);
        else change_point(k * 2 + 1);
        tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
    }
    
    int ans_p;
    void ask_point(int k) {
        if(tree[k].l == tree[k].r) {
            ans_p = tree[k].w;
            return ;
        }
        
        if(tree[k].f) Lazydown(k);
        int mid = (tree[k].l + tree[k].r) >> 1;
        if(mid >= l) ask_point(k * 2);
        else ask_point(k * 2 + 1);
    }
    
    int ans_i;
    void ask_interval(int k, int l, int r) {
        if(tree[k].l >= l && tree[k].r <= r) {
            ans_i += tree[k].w;
            return ;
        }
        
       	if(tree[k].f) Lazydown(k);
        int mid = (tree[k].r - tree[k].l) >> 1;
       	if(mid >= l) ask_interval(k * 2, l, r);
        if(mid < r) ask_interval(k * 2 + 1, l, r);
    }
    
    void change_interval(int k, int l, int r) {
        if(tree[k].l >= l && tree[k].r <= r) {
           tree[k].w += (tree[k].r - tree[k].l + 1) * x;
           tree[k].f += x;
           return ;
        }
        
    	if(tree[k].f) Lazydown(k);
        int mid = (tree[k].r - tree[k].l) >> 1;
       	if(mid >= l) change_interval(k * 2, l, r);
        if(mid < r) chnage_interval(k * 2 + 1, l, r);
        tree[k].w = tree[k * 2].w + tree[k * 2 + 1].w;
    }
    
    int main() {
        ...
    }
    
  • 相关阅读:
    中国大学MOOC-陈越、何钦铭-数据结构-2016秋期末考试
    Manifesto of the Communist Party
    PAT/字符串处理习题集(二)
    PAT/字符串处理习题集(一)
    PAT/进制转换习题集
    PAT/图形输出习题集
    2017-2018-2 《密码与安全新技术》第3周作业
    2017-2018-2 20179226 《网络攻防》第7周作业
    2017-2018-2 20179226 《网络攻防》第6周作业
    2017-2018-2 《密码与安全新技术》第2周作业
  • 原文地址:https://www.cnblogs.com/Nicoppa/p/11411199.html
Copyright © 2011-2022 走看看