线段树入门

简介

线段树是一种二叉树
它能把一些对于区间(或者线段)的修改、维护,从O(N)的时间复杂度变成O(logN)。

建树

先定义结构体

struct tree{
    ll l,r,data,num,lz;
}

递推到叶子再回溯累计

void built(ll i,ll l,ll r){
        t[i].l=l;
        t[i].r=r;
        if(l==r){
            t[i].data=a[l];
            return;
        }
        ll mid=(l+r)>>1; //取中点
        built(i*2,l,mid);
        built(i*2+1,mid+1,r); 
        t[i].data=t[i*2].data+t[i*2+1].data;  
}

(无pushdowm)

1.单点修改,区间查询

单点修改

和建树的方式差不多,递推找叶子,回溯更新

void change(int i,int dis,int k){
    if(t[i].l==t[i].r){
    	t[i].data+=k;
    	return;
    }
    if(dis>=t[2*i+1].l)change(2*i+1,dis,k);
    if(dis<=t[2*i].r)change(2*i,dis,k);
    t[i].data=t[2*i].data+t[2*i+1].data;
    return ;
}

区间查询

判断区间位置

ll query(int i,int l,int r){
    if(t[i].l>=l&&t[i].r<=r) return t[i].data;
    if(t[i].l>r||t[i].r<l)return 0;
    ll ans=0;
    if(t[i*2].r>=l) ans+=query(i*2,l,r);
    if(t[i*2+1].l<=r) ans+=query(i*2+1,l,r);
    return ans;
}

2.区间修改,单点查询

为了减少查询的时间花费,我们在结构体里面增多一项num,用来记录某区间修改前后的差值k,查询单个点的值时,向下寻找同时记录经过区间的k值

区间修改

void change(int i,int l,int r,int k){
    if(t[i].l>=l&&t[i].r<=r){
    	t[i].num+=k;
    	return ;
    }
    if(t[i*2].r>=l) change(i*2,l,r,k);
    if(t[i*2+1].l<=r) change(i*2+1,l,r,k);
    return;
}

单点查询

void query(int i,int dis){
    ans+=t[i].num;
    if(t[i].l==t[i].r) return ;
    if(dis>=t[i*2+1].l) query(i*2+1,dis);
    if(dis<=t[i*2].r) query(i*2,dis);
    return ;
}

(有pushdown)

区间修改,区间查询

(加减)

引入lazytage,被完全覆盖在目标区间的区间才记录lazytage,再向下传递标记

void push_down(int i)
{
    if(t[i].lz!=0)
    {
        t[i*2].lz+=t[i].lz;//左右儿子分别加上父亲的lz
        t[i*2+1].lz+=t[i].lz;
        int mid=(t[i].l+t[i].r)/2;
        t[i*2].data+=t[i].lz*(mid-t[i*2].l+1);//左右分别求和加起来
        t[i*2+1].data+=t[i].lz*(t[i*2+1].r-mid);
        t[i].lz=0;//父亲lz归零
    }
    return ;
}
void change(int i,int l,int r,int k)
{
    if(t[i].r<=r && t[i].l>=l)//如果当前区间被完全覆盖在目标区间里,讲这个区间的data+k*(t[i].r-t[i].l+1)
    {
        t[i].data+=k*(t[i].r-t[i].l+1);
        t[i].lz+=k;//记录lazytage
        return ;
    }
    push_down(i);//向下传递
    if(t[i*2].r>=l)
        change(i*2,l,r,k);
    if(t[i*2+1].l<=r)
        change(i*2+1,l,r,k);
    t[i].data=t[i*2].data+t[i*2+1].data;
    return ;
}
ll query(int i,int l,int r){
    if(t[i].l>=l && t[i].r<=r)
        return t[i].data;
    if(t[i].r<l || t[i].l>r)  return 0;
    push_down(i);
    ll ans=0;
    if(t[i*2].r>=l) ans+=query(i*2,l,r);
    if(t[i*2+1].l<=r) ans+=query(i*2+1,l,r);
    return ans;
}

(乘)

n>>1==n/2, n<<1|1=n*2+1
引入mlz,plz分别记录乘和加减
注意:建树的时候mlz初始化为1

inline void push_down(long long i){//注意这种级别的数据一定要开long long
    long long k1=t[i].mlz,k2=t[i].plz;
    t[i<<1].data=(t[i<<1].data*k1+k2*(t[i<<1].r-t[i<<1].l+1))%mod;//
    t[i<<1|1].data=(t[i<<1|1].data*k1+k2*(t[i<<1|1].r-t[i<<1|1].l+1))%mod;
    t[i<<1].mlz=(t[i<<1].mlz*k1)%mod;
    t[i<<1|1].mlz=(t[i<<1|1].mlz*k1)%mod;
    t[i<<1].plz=(t[i<<1].plz*k1+k2)%mod;
    t[i<<1|1].plz=(t[i<<1|1].plz*k1+k2)%mod;
    t[i].plz=0;
    t[i].mlz=1;
    return ;
}
void p_change(int i,int l,int r,int k)
{
    if(t[i].r<=r && t[i].l>=l)
    {
        t[i].data=(t[i].data+k*(t[i].r-t[i].l+1))%mod;
        t[i].plz=(t[i].plz+k)%mod;
        return ;
    }
    push_down(i);
    if(t[i*2].r>=l)
        p_change(i*2,l,r,k);
    if(t[i*2+1].l<=r)
        p_change(i*2+1,l,r,k);
    t[i].data=t[i*2].data+t[i*2+1].data;
    return ;
}
void m_change(int i,int l,int r,int k)
{
    if(t[i].r<=r && t[i].l>=l)
    {
        t[i].data=(t[i].data*k)%mod;
        t[i].mlz=(t[i].mlz*k)%mod;
        t[i].plz=(t[i].plz*k)%mod;
        return ;
    }
    push_down(i);//向下传递
    if(t[i*2].r>=l)
        m_change(i*2,l,r,k);
    if(t[i*2+1].l<=r)
        m_change(i*2+1,l,r,k);
    t[i].data=t[i*2].data+t[i*2+1].data;
    return ;
}
inline ll query(int i,int l,int r){
    if(t[i].l>=l && t[i].r<=r)
        return t[i].data;
    if(t[i].r<l || t[i].l>r)  return 0;
    push_down(i);
    ll ans=0;
    if(t[i*2].r>=l)  ans=(ans+query(i*2,l,r))%mod;
    if(t[i*2+1].l<=r)  ans=(ans+query(i*2+1,l,r))%mod;
    return ans;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值