ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

线段树

2019-04-03 12:38:17  阅读:236  来源: 互联网

标签:ch val int 线段 bj long rc


4N空间版

#include<iostream>
#include<cstdio>
using namespace std; 
const int SIZE=100010;
int N, M;

inline long long read()                             //快读可以定义为内联函数,效率更高 
{
    long long s=0, w=1;
    char ch=getchar();
    while(ch<'0'  || ch>'9' ){ if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0' && ch<='9'){ s=s*10+ch-'0';    ch=getchar(); }
    return s*w;
}

struct SegNode
{
    int l, r;                                       //[l, r]为当前结点表示的区间,bj为懒人标记 
    long long val, bj;                              //根据题目要求,一定注意数据范围! 
}ST[SIZE<<2];                                       //使用完全二叉树的固定位置法,空间要开到N的4倍 

void buildT(int x, int L, int R)                    //先序遍历构建线段树,叶子处读入数据(数据区间为[L, R])
{
    ST[x].l=L, ST[x].r=R, ST[x].bj=0;               //第一次调用buildT时,x为根结点,通常为1 
    if(L==R)                                        //已到达叶子结点 
    {
        ST[x].val=read();                           //读入数值 
        //ST[x].val=arr[L];                         //这种方式以一个数组初始化线段树 
        return ;
    }
    int mid=(L+R)>>1;
    buildT(x<<1, L, mid);                           //递归构建左、右子树 
    buildT((x<<1)+1, mid+1, R);                     //注意"+"的优先级高于"<<"的优先级,搞不清的话写x*2+1更安全 
    ST[x].val=ST[x<<1].val+ST[(x<<1)+1].val;        //回溯时合并左右子树的值 
}

                                                    //x有bj的含义:当前结点的val是正确的,但是bj还没有下传
                                                    //pushDown(x)的目的:让x左、右孩子结点的val正确,同时设置好其bj,清除x的bj 
void pushDown(int x)                                //当前结点的懒人标记下传 
{
    if(ST[x].bj && ST[x].l!=ST[x].r)                //有标记且x不是叶子结点,可以下传,从叶子结点下传可能会RE 
    {
        long long k=ST[x].bj;
        ST[x<<1].bj+=k;
        ST[x<<1].val+=k*(ST[x<<1].r-ST[x<<1].l+1);  //这里非常容易错,要乘k 
        ST[(x<<1)+1].bj+=k;
        ST[(x<<1)+1].val+=k*(ST[(x<<1)+1].r-ST[(x<<1)+1].l+1);  //这里非常容易错,要乘k
        ST[x].bj=0;                                 //清除x结点标记 
    }
}

void radd(int L, int R, int k, int x=1)             //区间加,[L, R] + k,x为根 
{
    if(L<=ST[x].l && ST[x].r<=R)                    //当前结点表示的区间被[L, R]覆盖,可直接被修改、设置懒人标记,停止递归 
    {
        ST[x].val+=k*(ST[x].r-ST[x].l+1);           //修改区间val至正确值 
        ST[x].bj+=k;                                //设懒人标记 
        return;
    }
    int m=(ST[x].l+ST[x].r)>>1;
    pushDown(x);                                    //要递归修改儿子,先把标记下传 
    if(L<=m)    radd(L, R, k, x<<1);                //递归修改左孩子 
    if(R>=m+1)  radd(L, R, k, (x<<1)+1);            //递归修改右孩子 
    ST[x].val=ST[x<<1].val+ST[(x<<1)+1].val;        //回溯时合并,更新父结点的值 
}
 
long long rquery(int L, int R, int x=1)             //区间和[L, R]查询,x为根 
{
    if(L<=ST[x].l && ST[x].r<=R)                    //当前结点表示的区间被[L, R]覆盖,可直接返回值 
        return ST[x].val;
    int m=(ST[x].l+ST[x].r)>>1;
    pushDown(x);                                    //要向孩子查询,先更新孩子的值 
    long long ans=0;
    if(L<=m)    ans+=rquery(L, R, x<<1);            //加左子树返回的值 
    if(R>=m+1)  ans+=rquery(L, R, (x<<1)+1);        //加右子树返回的值 
    return ans;
}

int main()
{
    N=read(), M=read();
    buildT(1, 1, N);
    for(register int i=1, c, l, r, k; i<=M; i++)    //频繁使用的循环遍历设置为寄存器类型(register)会带来一定效率提升 
    {
        c=read(), l=read(), r=read();
        if(c==1)    k=read(), radd(l, r, k);
        if(c==2)    printf("%lld\n", rquery(l, r));
    }
    return 0;
}

2N空间版(使用指针,也可以预分配空间,使用数组下标作为“指针”连接结点)

/*使用指针存储线段树,空间可以控制到2N,也可以开一个2N的数组,使用“逻辑指针”连接。 
*/ 
#include<iostream>
#include<cstdio>
using namespace std; 
const int SIZE=100010;
int N, M;

inline long long read()                             //快读可以定义为内联函数,效率更高 
{
    long long s=0, w=1;
    char ch=getchar();
    while(ch<'0'  || ch>'9' ){ if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0' && ch<='9'){ s=s*10+ch-'0';    ch=getchar(); }
    return s*w;
}

struct SegNode
{
    int l, r;                                       //[l, r]为当前结点表示的区间,bj为懒人标记 
    long long val, bj;                              //根据题目要求,一定注意数据范围! 
    SegNode *lc, *rc;                               //指向左、右子树的指针
    SegNode(int left, int right){ l=left, r=right, bj=0, lc=rc=NULL; } 
}; 

void buildT(SegNode * &x, int L, int R)             //x必须定义为引用,否则递归时无法修改指针的值
{
    x=new SegNode(L, R);                            //新建结点 
    if(L==R)                                        //已到达叶子结点 
    {
        x->val=read();
        //x->val=arr[L];                            //这种方式以一个数组初始化线段树 
        return ;
    }
    int mid=(L+R)>>1;
    buildT(x->lc, L, mid);                          //递归构建左、右子树 
    buildT(x->rc, mid+1, R); 
    x->val=x->lc->val+x->rc->val;                   //回溯时合并左右子树的值 
}

                                                    //x有bj的含义:当前结点的val是正确的,但是bj还没有下传
                                                    //pushDown(x)的目的:让x左、右孩子结点的val正确,同时设置好其bj,清除x的bj 
void pushDown(SegNode *x)                           //当前结点的懒人标记下传 
{
    if(x->bj && x->l!=x->r)                         //有标记且x不是叶子结点,可以下传,从叶子结点下传可能会RE 
    {
        long long k=x->bj;
        x->lc->bj+=k;
        x->lc->val+=k*(x->lc->r-x->lc->l+1);        //这里非常容易错,要乘k 
        x->rc->bj+=k;
        x->rc->val+=k*(x->rc->r-x->rc->l+1);        //这里非常容易错,要乘k
        x->bj=0;                                    //清除x结点标记 
    }
}

void radd(int L, int R, int k, SegNode *x)          //区间加,[L, R] + k,x为根 
{
    if(L<=x->l && x->r<=R)                          //当前结点表示的区间被[L, R]覆盖,可直接被修改、设置懒人标记,停止递归 
    {
        x->val+=k*(x->r-x->l+1);                    //修改区间val至正确值 
        x->bj+=k;                                   //设懒人标记 
        return;
    }
    int m=(x->l+x->r)>>1;
    pushDown(x);                                    //要递归修改儿子,先把标记下传 
    if(L<=m)    radd(L, R, k, x->lc);               //递归修改左孩子 
    if(R>=m+1)  radd(L, R, k, x->rc);               //递归修改右孩子 
    x->val=x->lc->val+x->rc->val;                   //回溯时合并,更新父结点的值 
}
 
long long rquery(int L, int R, SegNode *x)          //区间和[L, R]查询,x为根 
{
    if(L<=x->l && x->r<=R)                          //当前结点表示的区间被[L, R]覆盖,可直接返回值 
        return x->val;
    int m=(x->l+x->r)>>1;
    pushDown(x);                                    //要向孩子查询,先更新孩子的值 
    long long ans=0;
    if(L<=m)    ans+=rquery(L, R, x->lc);           //加左子树返回的值 
    if(R>=m+1)  ans+=rquery(L, R, x->rc);           //加右子树返回的值 
    return ans;
}

int main()
{
    SegNode *root=NULL;                             //定义一个根指针为空 
    N=read(), M=read();
    buildT(root, 1, N);
    for(register int i=1, c, l, r, k; i<=M; i++)    //频繁使用的循环遍历设置为寄存器类型(register)会带来一定效率提升 
    {
        c=read(), l=read(), r=read();
        if(c==1)    k=read(), radd(l, r, k, root);
        if(c==2)    printf("%lld\n", rquery(l, r, root));
    }
    return 0;
}

二维的线段树(加、乘两种操作)

标签:ch,val,int,线段,bj,long,rc
来源: https://www.cnblogs.com/lfyzoi/p/10648063.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有