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

    线段树

    • 基本概念

    线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。
    来自百度百科

    • 实现原理
      每个节点代表一个区间,在对线段操作的线段树中,每个点代表一条线段,在用线段树维护数列信息的时候,每个点代表一个数,但本质上都是每个点代表一个数。
      image
      从这个很丑的图可以看出:维护的还是最小的儿子节点

    1、每个节点的左孩子区间范围为[l,mid],右孩子为[mid+1,r]
    2、对于结点k,左孩子结点为2k,右孩子为2k+1,这符合完全二叉树的性质

    模板代码

    从PPT上复制总结下来的

    • 定义线段树
    struct Node{
        int left, right;
        …
    }node[MAXN*4];
    //一般开到四倍 即MAXN<<=2;
    
    • 建树 Build
      即初始化树
    #define L(u) (u<<1)//左孩子 即u*2
    #define R(u) (u<<1|1)//右孩子 即u*2+1
    void Build (int u,int left,int right){ //u表当前结点 
    //left right 表左区间范围[left,right]
        node[u].l = left,node[u].r = right;
        .........        //结点信息的初始化
        if (node[u].l == node[u].r){//到叶结点 return
            .........//某些赋值
            return ;
        }
        int mid = (node[u].l + node[u].r)>>1;
        Build (L(u),left,mid);
        Build (R(u),mid+1,right);
        Pushup(u);
    }
    
    
    • 向上回溯 Pushup
      该函数就是由子结点递归回来 修改父结点中的信息
    void Pushup(int u){ 
    node[u].sum = node[L(u)].sum + node[R(u)].sum;//价值
        …….
        …… return ;
    }
    
    
    • 延时更新 Pushdown
      调用此函数时,结点中一般有个状态标志位 用来判断是否需要往下更新,可以防TLE 只在查询的时候修改
    void Pushdown (int u) {//延迟覆盖操作
    if (node[u].state == -1) {
    //更新父结点,左右子结点信息 }
    else if (node[u].state == 1){
    //更新父结点,左右子结点信息
    }
        else {
    	………
    }
        
    }
    
    
    • 更新修改操作 Update

    点的修改

    void Update(int L,int C,int l,int r,int rt){//l,r表示当前节点区间,rt表示当前节点编号
    	if(l==r){//到叶节点,修改 
    		Sum[rt]+=C;
    		return;
    	}
    	int m=(l+r)>>1;
    	//根据条件判断往左子树调用还是往右 
    	if(L <= m) Update(L,C,l,m,rt<<1);
    	else       Update(L,C,m+1,r,rt<<1|1);
    	PushUp(rt);//子节点更新了,所以本节点也需要更新信息 
    } 
    

    区间的修改

    void Update(int L,int R,int C,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号 
    	if(L <= l && r <= R){//如果本区间完全在操作区间[L,R]以内 
    		Sum[rt]+=C*(r-l+1);//更新数字和,向上保持正确
    		Add[rt]+=C;//增加Add标记,表示本区间的Sum正确,子区间的Sum仍需要根据Add的值来调整
    		return ; 
    	}
    	int m=(l+r)>>1;
    	PushDown(rt,m-l+1,r-m);//下推标记
    	//这里判断左右子树跟[L,R]有无交集,有交集才递归 
    	if(L <= m) Update(L,R,C,l,m,rt<<1);
    	if(R >  m) Update(L,R,C,m+1,r,rt<<1|1); 
    	PushUp(rt);//更新本节点信息 
    }
    

    以上都以求和为例

    • 查询操作 Query
    Data Query(int u,int left,int right)
    {
         if (left <= node[u].l&&node[u].r <= right)
         return node[u].sum;
         Pushdown(u);//延迟更新
         int mid = (node[u].l + node[u].r)>>1;
         if (right <= mid) return Query(L(u),left,right);
         else if (left > mid) return Query(R(u),left,right);
         else return (Query(L(u),left,mid) + Query(R(u),mid+1,right));
         Pushup(u);
    }
    
    

    练习

    1.EXAM 1711 http://exam.upc.edu.cn/problem.php?id=1711
    维护的应该是区间的最小值,很容易T,需要输入挂,延迟更新应该没有用到,然后其实这道题正解不是线段树,但是可以用用线段树的思想:

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=1e6+7;
    int minn[maxn<<2],add[maxn<<2],s[maxn];
    #define l(u) (u<<1)
    #define r(u) (u<<1|1)
    inline bool scan_d(int &num)
    {
        char in;
        bool IsN=false;
        in=getchar();
        if(in==EOF) return false;
        while(in!='-'&&(in<'0'||in>'9')) in=getchar();
        if(in=='-')
        {
            IsN=true;
            num=0;
        }
        else num=in-'0';
        while(in=getchar(),in>='0'&&in<='9')
        {
    
            num*=10,num+=in-'0';
    
        }
        if(IsN) num=-num;
        return true;
    }
    struct node
    {
        int l,r;
    } t[maxn<<2];
    void pushup(int u)//向上更新区间最小值
    {
        minn[u]=min(minn[l(u)],minn[r(u)]);
    }
    void pushdown(int u)
    {
        minn[u]-=add[u];
        add[l(u)]+=add[u];
        add[r(u)]+=add[u];
        add[u]=0;
    }
    void build(int u,int l,int r)
    {
        t[u].l=l,t[u].r=r;
        if(l==r)
        {
            minn[u]=s[l];
            return;
        }
        if(l>r) return;
        int mid=(l+r)>>1;
        build(l(u),l,mid);
        build(r(u),mid+1,r);
        pushup(u);
    }
    void update(int u,int al,int ar,int x)
    {
        if(add[u])pushdown(u);//如果u区间还没有处理
        if(al<=t[u].l&&t[u].r<=ar)
        {
            add[u]=x;
            pushdown(u);
            return;
        }
        if(al>t[u].r||ar<t[u].l)return;
        update(l(u),al,ar,x);
        update(r(u),al,ar,x);
        pushup(u);
    }
    int main()
    {
        int flag=0;
        int n,m;
        scan_d(n);
        scan_d(m);
        for(int i=1; i<=n; i++) scan_d(s[i]);
        build(1,1,n);
        int d,x,y;
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d%d",&d,&x,&y);
            update(1,x,y,d);
            if(minn[1]<0&&!flag)
            {
                printf("-1
    ");
                flag=i;
            }
        }
        printf("%d
    ",flag);
        return 0;
    }
    
    
    不要忘记努力,不要辜负自己 欢迎指正 QQ:1468580561
  • 相关阅读:
    无线鼠标换电池了
    Jython Interactive Servlet Console YOU WILL NEVER KNOW IT EXECLLENT!!! GOOD
    Accessing Jython from Java Without Using jythonc
    jython podcast cool isnt't it?
    Python里pycurl使用记录
    Creating an Interactive JRuby Console for the Eclipse Environment
    微软为AJAX和jQuery类库提供CDN服务
    Download A File Using Cygwin and cURL
    What is JMRI?这个是做什么用的,我真没看懂但看着又很强大
    用curl 发送指定的大cookie的http/https request
  • 原文地址:https://www.cnblogs.com/smallocean/p/9380249.html
Copyright © 2011-2022 走看看