参考 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\n",query(a,b,root));
        }
        else
        {
            scanf("%d%d%d",&a,&b,&c);
            update(a,b,c,root);
        }
    }
    return 0;
}