线段树(加法,乘法,加乘混合)

        重要:如果你是在学习线段树过程中遇到了一些不清楚的地方,想来碰碰运气,启发灵感寻找答案,那么恭喜你,来对了。

        但如果你是线段树小白,向通过这篇文章来从0开始学习线段树,很遗憾,本文不太适合你。文章基于这篇博文(线段树 从入门到进阶(超清晰,简单易懂)_繁凡さん的博客-CSDN博客_线段树)来写的,主要是本人在对这篇博文进行学习的时候所遇到的一些不解来进行解释和代码呈现。

        好了,废话不多说,直接呈现代码,学习过程的所有不解都是通过在相应代码处以注解的形式进行呈现。

                                                        区间求和+单点修改

#include <iostream>
#include <algorithm>
#include <vector>
#include <ctime>

using namespace std;


//区间求和,单点修改
struct node
{
    int L,R,sum;
    node():sum(0){}
};

int nums[6]={1,2,3,4,5,6};
node tree[2*6+1];

//数组模拟线段树,以n节点为父节点,子左节点为n*2,子右节点为n*2+1
void build(int index,int L,int R)
{
    if(L==R)
    {
        tree[index].L=L;
        tree[index].R=R;
        tree[index].sum=nums[L];
        return;
    }

    int mid=(L+R)>>1;
    build(index*2,L,mid);
    build(index*2+1,mid+1,R);

    tree[index].L=L;
    tree[index].R=R;
    tree[index].sum=tree[index*2].sum+tree[index*2+1].sum;
}

int sum(int index,int L,int R)  //区间求和
{
    if(tree[index].L>=L&&tree[index].R<=R) return tree[index].sum;

    if(tree[index].R<L) return 0;
    else if(tree[index].L>R) return 0;

    int ans=0,mid=(tree[index].L+tree[index].R)>>1;

    if(tree[index*2].R>=L) ans+=sum(index*2,L,R);      //和左子树有交集
    if(tree[index*2+1].L<=R) ans+=sum(index*2+1,L,R);  //和右子树有交集

    //也可以这样写
    //if(L<=mid) ans+=sum(index*2,L,R);    //判断条件由创建树的时候决定的
    //if(R>mid) ans+=sum(index*2+1,L,R);

    return ans;
}

void add(int index,int dis,int add_num)
{
    if(tree[index].R<dis||tree[index].L>dis) return;
    if(tree[index].L==tree[index].R)
    {
        tree[index].sum+=add_num;
        return;
    }

    if(tree[index*2].R>=dis)
    {
        add(index*2,dis,add_num);
    }
    else
    {
        add(index*2+1,dis,add_num);
    }
    tree[index].sum+=add_num;
}

int main()
{
    int left=0,right=5;

    build(1,left,right);
    cout<<sum(1,left,right)<<endl;
    add(1,left+1,4);
    cout<<sum(1,left+1,left+1)<<endl;
    cout<<sum(1,left,right)<<endl;
    return 0;
}










                                                        区间修改+单点查询 

//区间修改但只有单点查询
class my_tree
{
private:
    int *nums;

    struct node
    {
        int L,R,num;
    }tree[201];

    void build(int left,int right,int index=1)  //缺省,默认从1节点开始建树
    {
        //除了叶子,其他节点的num全部设置为0
        if(left==right)
        {
            tree[index]={left,right,nums[left]};
            return;
        }
        tree[index]={left,right,0};  //注意这里标记为0,而不是左右子树之和
        int mid=(left+right)>>1;
        build(left,mid,index<<1);
        build(mid+1,right,index<<1|1);

    }


public:
    my_tree(int nums_tree[],int left,int right)
    {
        nums=nums_tree;
        build(left,right);
    }

    void modify(int left,int right,int k,int index=1)  //标记
    {
        if(tree[index].L>=left&&tree[index].R<=right)
        {
            tree[index].num+=k;
            return;
        }

        int mid=(tree[index].L+tree[index].R)>>1;
        if(left<=mid) modify(left,right,k,index<<1);
        if(right>mid) modify(left,right,k,index<<1|1);
    }

    int query(int k,int index=1)
    {
        if(tree[index].L==tree[index].R) return tree[index].num;

        int mid=(tree[index].L+tree[index].R)>>1;
        if(k<=mid) return tree[index].num+query(k,index<<1);
        else return tree[index].num+query(k,index<<1|1);
    }

};

int main()
{
    int len=8;
    int nums[]={1,2,3,4,5,6,7,8};

    my_tree Tree=my_tree(nums,0,7);

    cout<<Tree.query(2)<<endl;
    Tree.modify(1,2,10);
    cout<<Tree.query(2)<<" "<<Tree.query(1);
    return 0;
}


                                        先乘后加线段树+区间修改+区间查询

//先乘后加线段树+区间修改+区间查询
class my_tree
{
private:
    int *nums;

    struct node
    {
        int L,R,num,mul_lazytage,plus_lazytage;  //换成一个乘法懒标记和一个加法懒标记
    }tree[201];

    void build(int left,int right,int index=1)  //缺省,默认从1节点开始建树
    {
        //根节点num不能再是0了,乘法懒标记初始为1,加法懒标记初始为0
        if(left==right)
        {
            tree[index]={left,right,nums[left],1,0};
            return;
        }
        tree[index]={left,right,0,1,0};  //num=0是为了占位置
        int mid=(left+right)>>1;
        build(left,mid,index<<1);
        build(mid+1,right,index<<1|1);
        tree[index].num=tree[index<<1].num+tree[index<<1|1].num;
    }

    inline void pushdown(int index)  //无递归,考虑设为内置函数,提高效率
    {
        if(tree[index].mul_lazytage!=1||tree[index].plus_lazytage!=0)
        {
            int k_mul=tree[index].mul_lazytage,k_plu=tree[index].plus_lazytage;
            int &left_mul=tree[index<<1].mul_lazytage,&left_plu=tree[index<<1].plus_lazytage;
            int &left_L=tree[index<<1].L,&left_R=tree[index<<1].R;
            int &left_num=tree[index<<1].num;

            int &right_mul=tree[index<<1|1].mul_lazytage,&right_plu=tree[index<<1|1].plus_lazytage;
            int &right_L=tree[index<<1|1].L,&right_R=tree[index<<1|1].R;
            int &right_num=tree[index<<1|1].num;

            //更新区间总和
            tree[index<<1].num=tree[index<<1].num*k_mul+k_plu*(tree[index<<1].R-tree[index<<1].L+1);
            tree[index<<1|1].num=tree[index<<1|1].num*k_mul+k_plu*(tree[index<<1|1].R-tree[index<<1|1].L+1);

            //先更新乘
            left_mul*=k_mul;
            right_mul*=k_mul;

            //更新加法
            //原来子节点如果存在加的数,那么更新子节点的加会和乘不一样
            //原子节点加的数*根节点乘的数+根节点加的数
            left_plu=left_plu*k_mul+k_plu;
            right_plu=right_plu*k_mul+k_plu;

            tree[index].mul_lazytage=1;
            tree[index].plus_lazytage=0;
        }
    }


public:
    my_tree(int nums_tree[],int left,int right)
    {
        nums=nums_tree;
        build(left,right);
    }

    void modify(int left,int right,int mul,int plu,int index=1)  //标记
    {
        if(tree[index].L>=left&&tree[index].R<=right)
        {
            tree[index].num*=mul;  //先乘后加
            tree[index].num+=plu*(tree[index].R-tree[index].L+1); //区间总和要更新,因为不再更新子节点了

            //更新懒标记
            tree[index].mul_lazytage*=mul;
            tree[index].plus_lazytage+=plu;
            return;
        }

        //判断条件放在pushdown里面,方便写代码
        //如果在之前标记时该区间是被全包围,会造成它的子节点没有更新,
        //而现在标记时不再是全包围,需要用它的子节点,所以
        //那就需要将之前的lazytage先传给它的子节点了,将子节点先更新,再进行当前的更新
        pushdown(index);

        int mid=(tree[index].L+tree[index].R)>>1;
        if(left<=mid) modify(left,right,mul,plu,index<<1);
        if(right>mid) modify(left,right,mul,plu,index<<1|1);

        tree[index].num=tree[index<<1].num+tree[index<<1|1].num; //别忘了更新当前区间的总和
    }

    int query(int left,int right,int index=1)
    {
        if(tree[index].L>right||tree[index].R<left) return 0;
        if(tree[index].L>=left&&tree[index].R<=right) return tree[index].num;

        //如果当前节点管辖区间不是被全包围的,那就要pushdown
        pushdown(index);

        int mid=(tree[index].L+tree[index].R)>>1,ans=0;
        if(left<=mid) ans+=query(left,right,index<<1);
        if(right>mid) ans+=query(left,right,index<<1|1);
        return ans;
    }

};

int main()
{
    int len=8;
    int nums[]={1,2,3,4,5,6,7,8};

    my_tree Tree=my_tree(nums,0,7);

    cout<<Tree.query(1,2)<<" "<<Tree.query(2,3)<<endl;
    Tree.modify(1,3,2,1);
    cout<<Tree.query(1,2)<<" "<<Tree.query(2,3)<<endl;
    return 0;
}

快乐的文章总是如此的短暂,如果有帮助到你,那就留下你的足迹吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值