简介
线段树是一种二叉树
它能把一些对于区间(或者线段)的修改、维护,从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;
}