• 【NOIp复习】数据结构之线段树


    线段树

    • 会用到的公式:将完全二叉树从左到右、从上到下依次编号,设当前结点编号为n
    • 左节点编号为:2*n 右节点编号为2*n+1
    • 把空节点的编号初始化为-1
    • 线段树的本质其实是二叉搜索树啦,所以说可以很方便的解决区间最大最小、查询区间和、修改区间和的问题
    • 用数组表示线段树的话,如果本身的区间长度为n,线段树节点数2*n左右,开3*n的大小很保险
    • 设线段树上节点代表区间为[a,b],那么左子树代表[a,(a+b)/2],右子树代表[(a+b)/2,b]
    • 如果a==b,那么该节点为叶子结点,否则递归建树

    先写了个很难看的代码…(并不能作为模板,模板在后面)

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <iostream>
    #define maxn 50010
    #define REP(a,b) for(int i=a;i<=b;i++) 
    using namespace std;
    
    struct node{
        int left,right,val;//左右端点(代表该节点覆盖线段范围)、权值 
    }tree[maxn*3];
    
    int s[maxn];//这里的s是读入的待建树数组
    int ans=0;//查询的区间和累加在ans上,注意每次查询之后都要重置ans为0
    
    void create(int l, int r, int num){//建树操作,左右端点与节点编号作为传入参数 
        tree[num].left=l;
        tree[num].right=r;
        if(l==r) {
            tree[num].val=s[l]; return;
        }
        else {
            create(l,(l+r)/2,num*2);
            create((l+r)/2+1,r,num*2+1);
            tree[num].val=tree[num*2].val+tree[num*2+1].val;//这里以维护区间和为例 
            return;
        }
    }
    
    void add(int a, int b, int num){//第a个位置增加b,当前修改节点编号为num 
        tree[num].val+=b;
        if(tree[num].left==tree[num].right) return;//已经修改到叶子了,不必再向下修改
        else{
            if(a>(tree[num].left+tree[num].right)/2) add(a,b,num*2+1);//如果a在num节点的右区间,访问右子树
            else add(a,b,num*2);
            return;
        }
    }
    
    void ask(int l, int r, int num){//查询从l到r的区间和,当前考察节点编号为num 
        if(l<=tree[num].left&&r>=tree[num].right){//如果该节点存放的是待查找区间的一部分,就可以把这个节点累加上去而不必再往下搜索了 
            ans+=tree[num].val;
            return;
        }
        if(l>tree[num].right||r<tree[num].left) return;
        else{
            int mid=(tree[num].left+tree[num].right)/2;
            if(l>mid) {
                ask(l,r,num*2+1);
                return;
            }
            else if(r<mid) {
                ask(l,r,num*2); return;
            }
            else {
                ask(l,r,num*2);
                ask(l,r,num*2+1);
                return;
            }
            return;
        }
    }

    线段树的区间修改(lazy思想)

    首先是改进了上一段代码的表示方法,不将num节点所存储的区间端点放在结构体中,而是放在函数中作为参数传入,可以用macro简化代码。【代码中的大写字母L,R都代表num节点的左右端点,小写字母l,r代表查询或修改区间的左右端点】
    其次是用pushDown函数和lazy标记实现了区间修改,因为区间修改没有必要在查询到它之前去向下传递,在修改时只需要检查当前num节点是否有lazy,如果有,pushDown之后看修改区间在什么位置对左右子树进行递归操作。查询和修改几乎完全一样。

    #include <cstdio>
    #include <cstring>
    
    #define maxn 100000+10
    #define lson L,mid,rt<<1 //左子树 左端点,右端点,编号
    #define rson mid+1,R,rt<<1|1
    #define root L,R,rt
    
    struct node{
        int val, lazy;
    }T[maxn<<2]; 
    
    void pushUp(int num){//num节点的左右子树都已经更新完毕了再pushUp更新num本身 
        T[num].val=T[num<<1].val+T[num<<1|1].val;
    }
    
    void pushDown(int L,int R,int num){
        int mid=(L+R)>>1;
        T[num<<1].val=T[num].lazy*(mid-L+1);
        T[num<<1|1].val=T[num].lazy*(R-mid);
        T[num<<1].lazy=T[num].lazy;
        T[num<<1|1].lazy=T[num].lazy;
        T[num].lazy=0;
    }
    
    void build(int L,int R,int num){
        if(L==R){
            scanf("%d",&T[num].val);
            return;
            //如果数组s[i]是已经读入的数据的话
            //T[num].val=s[L]; return; 
        }
        int mid=(L+R)>>1;
        build(lson);
        build(rson);
        pushUp(num);//左右子树都计算完成了就可以把自己算出来啦 
    }
    
    void update(int l,int r,int v,int L,int R,int rt){
    //在覆盖[L,R]的rt节点的[l,r]区间增加v 
        if(l==L&&r==R){
            T[rt].lazy=v;
            T[rt].val=v*(R-L+1);
            return;
        }
        int mid=(L+R)>>1;
        if(T[rt].lazy) pushDown(root);
        if(r<=mid) update(l,r,v,lson);//注意向下取整左取等右不取等 
        else if(l>mid) update(l,r,v,rson);
        else{
            update(l,mid,v,lson);
            update(mid+1,r,v,rson);
        }
        pushUp(num);
    }
    
    int query(int l,int r,int L,int R,int rt){
        if(l==L&&r==R) return T[rt].val;
        int mid=(L+R)>>1;
        if(T[rt].lazy) pushDown(root);
        if(r<=mid) return query(l,r,lson);
        else if(l>mid) return query(l,r,rson);
        return query(l,mid,lson)+query(mid+1,r,rson);
    }
    
    int main(){
        int n,q;
        scanf("%d",&n);
        build(1,n,1);//从根节点开始建树
        scanf("%d",&q);
        while(q--){
            //应对多组查询 
        } 
        return 0;
    }

    练习题

    HDU 1166 单点修改,区间查询,用树状数组也可以实现
    HDU 1754 查询区间最大值
    HDU 1698 区间修改,区间查询,lazy思想
    HDU 1394 求逆序数,也可以用树状数组,记录每个位置与左边的差
    POJ 2777 位运算,区间修改

  • 相关阅读:
    算法导论4线性时间与暴力寻找最大子数组
    算法导论4.1DivideAndConquer寻找最大子数组
    算法导论2.3算法设计分治法合并排序
    算法导论第二章算法入门2.1 插入排序
    eclipse 从已经存在代码建工程
    centos6.3 eclipse cdt
    Cant open file /data/svn/dev/db/txn-current-lock: Permission denied的解决方法
    git命令的安装与github简单使用
    CentOS-6.3安装配置SVN
    github使用_转
  • 原文地址:https://www.cnblogs.com/leotan0321/p/6081369.html
Copyright © 2020-2023  润新知