线段树

内容来自石万东学长课件~

1.问题
给你一个数的序列A1A2……An。 并且可能多次进行下列两个操作

       1、对序列里面的某个数进行加减

       2、询问这个序列里面任意一个连续的子序列AiAi+1……Aj的和是多少。
希望第2个操作每次能在log(n)时间内完成

2.线段树

注意:

线段树的分解:

        如何从[1,9]分解出[2,8]

分解的规则就是:如果有某个节点代表的区间,完全属于待分解区间,则该节点为“终止”节点,不再继续往下分解
所有“终止”节点所代表的区间都不重叠,且加在一起就恰好等于整个待分解区间


线段树的特征(时间复杂度):

1、线段树的深度不超过log2(n)+1(向上取整,n是根节点对应区间的长度)。

2、线段树上,任意一个区间被分解后得到的“终止节点”数目都是log(n)量级。
线段树能在O(log(n))的时间内完成插入数据,更新数据、查找、统计等工作,提供了理论依据


线段树的题目分类:
可分为以下四大类:
1、单点更新:最最基础的线段树,只更新叶子节点,然后回溯更新其父亲结点
2、成段更新:(通常这对初学者来说是一道坎),需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候

3、区间合并 这类题目会询问区间中满足条件的连续最长区间,所以回溯的时候需要对左右儿子的区间进行合并(hdu 1540)
4、扫描线:这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去 最典型的就是矩形面积并,周长并等题(poj 1151)


一、点更新:

建树:

left和reight代表【2,8】的2和8;
struct node{
    int l,r,maxn,sum; //表示最大值和总和
 }tree[N<<2];

void build(int m,int l,int r){
    tree[m].l = l;
    tree[m].r = r;
    if(l == r){
        tree[m].maxn = a[l];
        tree[m].sum = a[l];
        return ;
    }
    int mid = (l+r)>>1;
    build(m<<1,l,mid);
    build((m<<1)+1,mid+1,r);
    tree[m].maxn = max(tree[m<<1].maxn,tree[(m<<1)+1].maxn);
    tree[m].sum = tree[m<<1].sum + tree[(m<<1)+1].sum;
}

更新:

void update(int m,int a,int val){
    if(tree[m].l == a && tree[m].r == a){
        tree[m].maxn = val;//tree[m].maxn += val;
        tree[m].sum = val;//tree[m].sum += val;
        return ;
    }
    int mid = (tree[m].l+tree[m].r)>>1;
    if(a <= mid)
        update(m<<1,a,val);
    else
        update((m<<1)+1,a,val);
    tree[m].maxn = max(tree[m<<1].maxn,tree[(m<<1)+1].maxn);
    tree[m].sum = tree[m<<1].sum + tree[(m<<1)+1].sum;
}

查询:
int query_max(int m,int l,int r){
    if(l == tree[m].l && r == tree[m].r)
        return tree[m].maxn;
        //return tree[m].sum;
    int mid = (tree[m].l+tree[m].r)>>1;
    if(r <= mid)
        return query_max(m<<1,l,r);
    if(l > mid)
        return query_max((m<<1)+1,l,r);
    return max(query_max(m<<1,l,mid), query_max((m<<1)+1,mid+1,r));
    //return query_sum(m<<1,l,mid) + query_sum((m<<1)+1,mid+1,r);
}

build(1,1,n);
update(1,a,b);
query(1,a,b);

二、段更新

延迟标记精讲:点击打开链接

struct node{
    ll l,r;
    ll addv,sum;
}tree[maxn<<2];

void maintain(int id)
{
    if(tree[id].l >= tree[id].r)
        return ;
    tree[id].sum = tree[id<<1].sum + tree[id<<1|1].sum;
}

void pushdown(int id)
{
    if(tree[id].l >= tree[id].r)
        return ;
    if(tree[id].addv){
        int tmp = tree[id].addv;
        tree[id<<1].addv += tmp;
        tree[id<<1|1].addv += tmp;
        tree[id<<1].sum += (tree[id<<1].r - tree[id<<1].l + 1)*tmp;
        tree[id<<1|1].sum += (tree[id<<1|1].r - tree[id<<1|1].l + 1)*tmp;
        tree[id].addv = 0;
    }
}

void build(int id,ll l,ll r)
{
    tree[id].l = l;
    tree[id].r = r;
    tree[id].addv = 0;
    tree[id].sum = 0;
    if(l==r)
    {
        tree[id].sum = 0;
        return ;
    }
    ll mid = (l+r)>>1;
    build(id<<1,l,mid);
    build(id<<1|1,mid+1,r);
    maintain(id);
}

void updateAdd(int id,ll l,ll r,ll val)
{
    if(tree[id].l >= l && tree[id].r <= r)
    {
        tree[id].addv += val;
        tree[id].sum += (tree[id].r - tree[id].l+1)*val;
        return ;
    }
    pushdown(id);
    ll mid = (tree[id].l+tree[id].r)>>1;
    if(l <= mid)
        updateAdd(id<<1,l,r,val);
    if(mid < r)
        updateAdd(id<<1|1,l,r,val);
    maintain(id);
}

void query(int id,ll l,ll r)
{
    if(tree[id].l >= l && tree[id].r <= r){
        anssum += tree[id].sum;
        return ;
    }
    pushdown(id);
    ll mid = (tree[id].l + tree[id].r)>>1;
    if(l <= mid)
        query(id<<1,l,r);
    if(mid < r)
        query(id<<1|1,l,r);
    maintain(id);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值