线段树总结

Posted xishirujin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树总结相关的知识,希望对你有一定的参考价值。

高级数据结构——线段树总结

本蒟蒻最近在做线段树的题,做了一小部分,有感而发,故写下这篇博客,如有错误,请大佬指出。

线段树,作为一种高级数据结构,而其作用与分块、树状数组均有一脉相承的部分,而且有的题均可以使用上面的两种算法去解决(当然只是一部分题)
对于线段树的介绍,我也就不再多说了,即对于数列将其放在树上进行维护一些值,在题目的要求下进行修改和更新,最后得到答案。

首先我们先上一两道模板题

1、洛谷P3372线段树1(加操作)

这是一道十分经典的线段树模板,当然他的解法也有其他方法,(十分重要)。
下面是代码:

#include<bits/stdc++.h>
#define ll long long
const int N=5e6+8;
using namespace std;
ll n,m,tag[N],ans[N],a[N];
ll ls(ll x)
    return x<<1;
//左儿子扩展 (x*2) 
ll rs(ll x)
    return x<<1|1;
//右儿子扩展(x*2+1) 
void up(ll x)
    ans[x]=ans[ls(x)]+ans[rs(x)];
// 求出当前点的总和值 
void build(ll l,ll r,ll p)
    if(l==r)
        ans[p]=a[l];
        return;
    
    ll mid=(l+r)>>1;
    build(l,mid,ls(p));//左儿子建树 
    build(mid+1,r,rs(p));//右儿子建树
    up(p);//进行整合 

void f(ll l,ll r,ll p,ll k)//k是懒(延迟)标记的数值 
    tag[p]+=k;
    ans[p]+=(r-l+1)*k;
//tag[]懒标记点。
void down(ll l,ll r,ll p)
    ll mid=(l+r)>>1;
    f(l,mid,ls(p),tag[p]);//左传 
    f(mid+1,r,rs(p),tag[p]);//右传 
    tag[p]=0;//懒标记传递结束,清零 
 
void xg(ll xl,ll xr,ll l,ll r,ll p,ll k)//修改操作 
    if(l>=xl&&xr>=r)//当前区间被所查询的区间包含 
        ans[p]+=(r-l+1)*k;
        tag[p]+=k;
        return ; 
    
    down(l,r,p);//懒坐标传递 
    ll mid=(l+r)>>1;
    if(xl<=mid)
        xg(xl,xr,l,mid,ls(p),k);    
    
    if(xr>mid)
        xg(xl,xr,mid+1,r,rs(p),k);
     
    up(p);//更新该节点 
 
ll query(ll gl,ll gr,ll l,ll r,ll p)
    ll an=0;
    if(gl<=l&&gr>=r)
        return ans[p];
    
    ll mid=(l+r)>>1;
    down(l,r,p);
    if(gl<=mid)
        an+=query(gl,gr,l,mid,ls(p));
    
    if(gr>mid)
        an+=query(gl,gr,mid+1,r,rs(p));
    
    return an;

ll fl,x,y,z;
int main()
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
           
    build(1,n,1);
    for(int i=1;i<=m;i++)
        scanf("%d",&fl);
        if(fl==1)
            scanf("%lld%lld%lld",&x,&y,&z);
            xg(x,y,1,n,1,z);    
        
        if(fl==2)
            scanf("%lld%lld",&x,&y);
            printf("%lld\n",query(x,y,1,n,1));
        
    
    return 0;
 

下一道模板题:

2、洛谷P3373线段树2(加乘操作)

这道题与上一道题同理,up和down的部分需要修改一下即可。(再给大家一道双倍经验题(P2023 [AHOI2009]维护序列

下面就是代码:

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int N=5e6+7;
ll ans[N],a[N],at[N],ml[N];
ll fl,n,m,P,x,y,z;
ll ls(ll x)
    return x<<1;

ll rs(ll x)
    return x<<1|1;

void up(ll p)
    ans[p]=(ans[ls(p)]+ans[rs(p)])%P;

void build(ll l,ll r,ll p)
    at[p]=0;
    ml[p]=1;
    if(l==r)
        ans[p]=a[l];
        return;
    
    ll mid=(l+r)>>1;
    build(l,mid,ls(p));
    build(mid+1,r,rs(p));
    up(p);

void down(ll l,ll r,ll p)
    ml[ls(p)]=(ml[ls(p)]*ml[p])%P;
    ml[rs(p)]=(ml[rs(p)]*ml[p])%P;
    at[ls(p)]=(at[ls(p)]*ml[p])%P;
    at[rs(p)]=(at[rs(p)]*ml[p])%P;
    ans[ls(p)]=(ans[ls(p)]*ml[p])%P;
    ans[rs(p)]=(ans[rs(p)]*ml[p])%P;
    ml[p]=1;//乘懒标记传递完成,清零处理,乘法优先于加法,所以先计算乘法再计算加法 
    ll mid=(l+r)>>1;
    at[ls(p)]=(at[ls(p)]+at[p])%P;
    at[rs(p)]=(at[rs(p)]+at[p])%P;
    ans[ls(p)]=(ans[ls(p)]+(mid-l+1)*at[p])%P;
    ans[rs(p)]=(ans[rs(p)]+(r-mid)*at[p])%P;
    at[p]=0; 

void xg(ll gl,ll gr,ll l,ll r,ll p,ll k)
    if(l>=gl&&gr>=r)
        at[p]=(k+at[p])%P;
        ans[p]=((r-l+1)*k+ans[p])%P;
        return ;
    
    down(l,r,p);
    ll mid=(l+r)>>1;
    if(gl<=mid)
        xg(gl,gr,l,mid,ls(p),k);
    
    if(gr>mid)
        xg(gl,gr,mid+1,r,rs(p),k);
    
    up(p);
//修改+; 
void ch(ll cl,ll cr,ll l,ll r,ll p,ll k)
    if(l>=cl&&r<=cr)
        ml[p]=(ml[p]*k)%P;
        at[p]=(at[p]*k)%P;
        ans[p]=(ans[p]*k)%P;
        return ;
    
    down(l,r,p);
    ll mid=(l+r)>>1;
    if(cl<=mid)
        ch(cl,cr,l,mid,ls(p),k);
    
    if(cr>mid)
        ch(cl,cr,mid+1,r,rs(p),k);
    
    up(p);
//修改*; 
ll query(ll al,ll ar,ll l,ll r,ll p)
    ll an=0;
    if(al<=l&&ar>=r)
        return ans[p]%P;
    
    down(l,r,p);
    ll mid=(l+r)>>1;
    if(al<=mid)
        an=an+query(al,ar,l,mid,ls(p));
    
    if(ar>mid)
        an=an+query(al,ar,mid+1,r,rs(p));
    
    return an%P;

int main()
    scanf("%lld%lld",&n,/*&m,*/&P);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    sacnf("%lld",&m);
    build(1,n,1);
    for(int i=1;i<=m;i++)
        scanf("%lld",&fl);
        if(fl==1)
            scanf("%lld%lld%lld",&x,&y,&z);
            ch(x,y,1,n,1,z);
        
        if(fl==2)
            scanf("%lld%lld%lld",&x,&y,&z);
            xg(x,y,1,n,1,z);
        
        if(fl==3)
            scanf("%lld%lld",&x,&y);
            printf("%lld\n",(query(x,y,1,n,1))%P);
        
    
    
    return 0;

当你写完这三道模板题之后那么恭喜你,你已经线段树入门了。
下面,就是对于线段树的进一步的运用。

使用线段树维护方差,平均数等操作

3、P1471 方差

这道题的题面我就不再进行粘贴,具体的意思大概如下:
给定一个数列,其中有三个操作,
1、将L到R的区间内的数加上k。
2、查询L到R区间的平均数。
3、查询L到R区间的方差。
我表示这道题第一开始看的时候对于操作1,2基本就是十分木板的操作,但是当我看到第三个操作时,我有点蒙。
但是,将方差的公式进行展开,就可以得到一个十分明晰的思路
$$
\beginaligned \sum_i=1^n\left(x_i-\overlinex\right)^2 &=\sum_i=1^n\left(x_i^2-2 x_i \overlinex+\overlinex^2\right) \ &=\sum_i=1^n x_i^2+n \overlinex^2-2 \overlinex \sum_i=1^n x_i \ &=\sum_i=1^n x_i^2+n \overlinex^2-2 \overlinex \cdot n \overlinex \ &=\sum_i=1^n x_i^2-\overlinen \overlinex^2 \endaligned
$$

最后我们再将这个公式除上n得到最终的式子,这样我们就有了初步的思路,维护这个序列的两个值一个是序列和,一个是序列的平方和。这样我们就可以得到我们所求的答案。
现在我们来考虑如何使用线段树来维护平方和。
我们首先将平方和的公式展开
$$
(x+k)^2=x^2+2 k x+k^2
$$
我们可以知道这样就可以维护平方和:
代码:

#include <bits/stdc++.h> 
using namespace std;
const int N=1e6+10;
inline int read()
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
        if(ch=='-')
            f=-1;
         
        ch=getchar();
    
    while(ch>='0'&&ch<='9')
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar(); 
    
    return x*f;
 
struct node
    int l,r,siz;
    double ave,sum,tag;
t[N];
int n,m;
double a[N]; 
inline int ls(int x)return x<<1;
inline int rs(int x)return x<<1|1;
inline void upd(int x)
    t[x].ave=t[ls(x)].ave+t[rs(x)].ave;   
    t[x].sum=(t[ls(x)].sum+t[rs(x)].sum);

inline void build(int l,int r,int x)
//  cout<<1;
    t[x].l=l,t[x].r=r,t[x].siz=r-l+1;
    if(l==r)
        t[x].ave=a[l];
        t[x].sum=a[l]*a[l];
        return ;
    
    int mid=(l+r)>>1;
    build(l,mid,ls(x));
    build(mid+1,r,rs(x));
    upd(x);

inline void pushdown(int x)
    if(!t[x].tag) return;
    t[ls(x)].tag+=t[x].tag;
    t[rs(x)].tag+=t[x].tag;
    t[ls(x)].sum+=(2.0*t[ls(x)].ave*t[x].tag+t[ls(x)].siz*t[x].tag*t[x].tag);//维护平方和
    t[rs(x)].sum+=(2.0*t[rs(x)].ave*t[x].tag+t[rs(x)].siz*t[x].tag*t[x].tag);
    t[ls(x)].ave+=t[x].tag*t[ls(x)].siz;
    t[rs(x)].ave+=t[x].tag*t[rs(x)].siz;
    t[x].tag=0;
    return ;

inline void add(int l,int r,int x,double k)
    if(t[x].l>=l&&t[x].r<=r)
        t[x].sum+=(2.0*t[x].ave*k+t[x].siz*k*k);
        t[x].ave+=k*t[x].siz;
        t[x].tag+=k;
        return ;
    
    pushdown(x);
    int mid=(t[x].l+t[x].r)>>1;
    if(l<=mid)
        add(l,r,ls(x),k);
    
    if(r>mid)
        add(l,r,rs(x),k);
    
    upd(x);

inline double query(int l,int r,int x,bool flag)
    if(t[x].l>=l&&t[x].r<=r)
        if(flag==1)
        return t[x].ave;
        else return t[x].sum;
       
    pushdown(x);
    int mid=(t[x].l+t[x].r)>>1; 
    double res=0;
    if(l<=mid)
        res+=query(l,r,ls(x),flag);
    
    if(r>mid)
        res+=query(l,r,rs(x),flag); 
     
    return res;

int  main()
    scanf("%d%d",&n,&m); 
    for(int i=1;i<=n;i++)
        scanf("%lf",&a[i]);
    
    build(1,n,1); 

    while(m--)
        int op,l,r;
        double k;
        double ans1,ans2;
        op=read();
        if(op==1)
            scanf("%d%d%lf",&l,&r,&k);    
            add(l,r,1,k);           
        
        if(op==2)
            scanf("%d%d",&l,&r);
            ans1=query(l,r,1,1);      
            printf("%.4lf",(double)ans1/(r-l+1));
            puts("");
           
        if(op==3)
            scanf("%d%d",&l,&r);      
            ans1=query(l,r,1,1);
            ans2=query(l,r,1,0);      
            printf("%.4lf",(double)ans2/(r-l+1)-ans1/(r-l+1)*ans1/(r-l+1));
            puts("");
               
    
       
    return 0;

值得注意的是这道题的题目要求是实数范围内,所以千万千万不要忘掉使用,

double(手动滑稽

这就是线段树去维护不同值的一个十分好的题,也十分的考验思维。
下面,我们看下一题,一道维护0/1序列的题。


因为时间原因,所以就先写到这里,表示一定不会咕掉的(心虚),明天再更。

以上是关于线段树总结的主要内容,如果未能解决你的问题,请参考以下文章

权值线段与主席树总结

线段树总结

总结线段树

线段树学习总结

线段树总结

线段树总结