zoukankan      html  css  js  c++  java
  • 线段树详解

    参考 http://blog.csdn.net/metalseed/article/details/8039326

    一:线段树基本概念

    1:概述

    线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决

    连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN)!

    性质:父亲的区间是[a,b],(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b],线段树需要的空间为数组大小的四倍

    2:基本操作(demo用的是查询区间最小值)

    线段树的主要操作有:

    (1):线段树的构造 void buildtree(int node,int l,int r);

    主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,

    最后回溯的时候给当前节点赋值

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <queue>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    #define ls node<<1,l,m
    #define rs node<<1|1,m+1,r
    const int maxn=256; int Tree[maxn*4];void buildtree(int node,int l,int r) { if(l==r) scanf("%d",&Tree[node]);/*只有一个元素,节点记录该单元素*/ else { /*递归构造左右子树*/buildtree(ls);
            buildtree(rs);
    /*回溯时得到当前node节点的线段信息*/ Tree[node]=Tree[node<<1]+Tree[node<<1|1]; } }

    (2):区间查询int query(int node,int l,int r,int L,int R);

    (其中node为当前查询节点,l,r为当前节点存储的区间,L,R为此次query所要查询的区间)

    主要思想是把所要查询的区间[a,b]划分为线段树上的节点,然后将这些节点代表的区间合并起来得到所需信息

    int query(int node,int l,int r,int L,int R)
    {
        if(L<=l&&r<=R) return Tree[node];
        int m=(l+r)>>1;
        int ans=0;
        if(m>=L) ans+=query(ls,L,R);
        if(m<R) ans+=query(rs,L,R);
        return ans;
    }

    可见,这样的过程一定选出了尽量少的区间,它们相连后正好涵盖了整个[L,R],没有重复也没有遗漏。同时,考虑到线段树上每层的节点最多会被选取2个,一共选取的节点数也是O(log n)的,

    因此查询的时间复杂度也是O(log n)。

    线段树并不适合所有区间查询情况,它的使用条件是“相邻的区间的信息可以被合并成两个区间的并区间的信息”。即问题是可以被分解解决的。

    (3):区间或节点的更新及线段树的动态维护update(这是线段树核心价值所在,节点中的标记域可以解决N多种问题)

    动态维护需要用到标记域,延迟标记等。

    a:单节点更新

    void update(int node,int l,int r,int ind,int add)
    {
        if(l==r)
        {
            Tree[node]+=add;
            return ;
        }
        int m=(l+r)>>1;
        if(ind<=m) update(ls,ind,add);
        else update(rs,ind,add);
    /*回溯更新父节点*/
        Tree[node]+=add;
    }

    b:区间更新(线段树中最有用的)

    需要用到延迟标记,每个结点新增加一个标记,记录这个结点是否被进行了某种修改操作(这种修改操作会影响其子结点)。对于任意区间的修改,我们先按照查询的方式将其划分成线段树中的结点,然后修改这些结点的信息,并给这些结点标上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个结点p,并且决定考虑其子结点,那么我们就要看看结点p有没有标记,如果有,就要按照标记修改其子结点的信息,并且给子结点都标上相同的标记,同时消掉p的标记(优点在于,不用将区间内的所有值都暴力更新,大大提高效率,因此区间更新是最有用的操作)

    void change(node *p,int a,int b)
    {
        if(a<=p->left&&p->right<=b)//当前节点的区间包含在修改区间内
        {
            ......
            return ;
        }
        Push_Down(p);//把当前节点的标记向下传递
        int mid=(p->left+p->right)/2;//计算左右子节点的分隔点
        if(a<mid) change(p->lch,a,b);//和左孩子有交集,考察左子节点
        if(b>mid) change(p->rch,a,b);//和右孩子有交集,考察右子节点
        update(p);
    }

    3:主要应用

    (1):区间最值查询问题 (见模板1)

    (2):连续区间修改或者单节点更新的动态查询问题 (见模板2)

    二:典型模板

    模板1:

    RMQ,查询区间最值下标---min(可用ST算法)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <queue>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    #define MAXN 100
    #define MAXIND 256//线段树节点个数
    //构建线段树,目的:得到M数组.
    void build(int node,int b,int e,int M[],int A[])
    {
        if(b==e) M[node]=b;//只有一个元素,只有一个下标
        else
        {
            build(2*node,b,(b+e)/2,M,A);
            build(2*node+1,(b+e)/2+1,e,M,A);
            if(A[M[2*node]]<=A[M[2*node+1]]) M[node]=M[2*node];
            else M[node]=M[2*node+1];
        }
    }
    //找出区间 [i, j] 上的最小值的索引
    int query(int node,int b,int e,int M[],int A[],int i,int j)
    {
        int p1,p2;
        //查询区间和要求的区间没有交集
        if(i>e||j<b) return -1;
        if(b>=i&&e<=j) return M[node];
        p1=query(2*node,b,(b+e)/2,M,A,i,j);
        p2=query(2*node+1,(b+e)/2+1,e,M,A,i,j);
        if(p1==-1) return M[node]=p2;
        if(p2==-1) return M[node]=p1;
        if(A[p1]<=A[p2]) return M[node]=p1;
        return M[node]=p2;
    }
    int main()
    {
        int M[MAXIND];//下标1起才有意义,否则不是二叉树,保存下标编号节点对应区间最小值的下标.
        memset(M,-1,sizeof(M));
        int a[]={3,4,5,7,2,1,0,3,4,5};
        build(1,0,sizeof(a)/sizeof(a[0])-1,M,a);
        cout<<query(1,0,sizeof(a)/sizeof(a[0])-1,M,a,0,5)<<endl;
        return 0;
    }

    模板2:

    连续区间修改或者单节点更新的动态查询问题 (此模板查询区间和)(可用树状数组更简洁)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <queue>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    #define lson l,m,rt<<1
    #define rson m+1,r,rt<<1|1
    #define root 1,N,1
    #define LL long long
    const int maxn=111111;
    LL add[maxn<<2];
    LL sum[maxn<<2];
    void PushUp(int rt)
    {
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    void PushDown(int rt,int m)
    {
        if (add[rt])
        {
            add[rt<<1]+=add[rt];
            add[rt<<1|1]+=add[rt];
            sum[rt<<1]+=add[rt]*(m-(m>>1));
            sum[rt<<1|1]+=add[rt]*(m>>1);
            add[rt]=0;
        }
    }
    void build(int l,int r,int rt)
    {
        add[rt]=0;
        if (l==r)
        {
            scanf("%lld",&sum[rt]);
            return ;
        }
        int m=(l+r)>>1;
        build(lson);
        build(rson);
        PushUp(rt);
    }
    void update(int L,int R,int c,int l,int r,int rt)
    {
        if (L<=l&&r<=R)
        {
            add[rt]+=c;
            sum[rt]+=(LL)c*(r-l+1);
            return ;
        }
        PushDown(rt,r-l+1);
        int m=(l+r)>>1;
        if (L<=m) update(L,R,c,lson);
        if (m<R) update(L,R,c,rson);
        PushUp(rt);
    }
    LL query(int L,int R,int l,int r,int rt)
    {
        if (L<=l&&r<=R) return sum[rt];
        PushDown(rt,r-l+1);
        int m=(l+r)>>1;
        LL ret=0;
        if (L<=m) ret+=query(L,R,lson);
        if (m<R) ret+=query(L,R,rson);
        return ret;
    }
    int main()
    {
        int N,Q;
        scanf("%d%d",&N,&Q);
        build(root);
        while (Q--)
        {
            char op[2];
            int a,b,c;
            scanf("%s",op);
            if (op[0]=='Q')
            {
                scanf("%d%d",&a,&b);
                printf("%lld
    ",query(a,b,root));
            }
            else
            {
                scanf("%d%d%d",&a,&b,&c);
                update(a,b,c,root);
            }
        }
        return 0;
    }
  • 相关阅读:
    poj3041(最小顶点覆盖)
    High-speed Charting Control--MFC绘制图表(折线图、饼图、柱形图)控件
    hdu 3183 A Magic Lamp(RMQ)
    Android studio 中创建AIDL Service
    cocos2d-x 3.0正式版 cmd创建project以及一键创建project
    【Machine Learning】决策树案例:基于python的商品购买能力预测系统
    【Machine Learning】机器学习及其基础概念简介
    【Machine Learning】Python开发工具:Anaconda+Sublime
    【HanLP】HanLP中文自然语言处理工具实例演练
    【HanLP】资料链接汇总
  • 原文地址:https://www.cnblogs.com/d-e-v-i-l/p/4744294.html
Copyright © 2011-2022 走看看