线段树——从入门到入土

简介

众所周知啊,与它齐名的还有个树状数组,那是一个简单亿倍的东西,问题不大。

线段树、是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界(毒瘤到炸),因此有时需要离散化让空间压缩。

 

这种东西啊,一般都处理可以运用结合律的东西(对,就是小学生都会的东西例如区间最值问题,或者是区间和什么的

线段树——从入门到入土_第1张图片

 

 

 例如这个,叶子结点就是单个的数,其中每个非叶子节点都是一段区间,并且也就是他儿子节点信息的整合(比如这里是min的问题)由此可见,线段树是二叉树。

建树

利用二叉树的性质可知一个点x的左右儿子分别是x*2和x*2+1,所以利用这个性质我们求出儿子

int ls(int p){return p<<1;}//左儿子
int rs(int p){return p<<1|1;}//又儿子

因为这个数是从下往上整理的,所以我们就可以搜索这个二叉树,一旦到了叶子结点,我们就可以给它赋值,等到一个节点的子节点做完了后,我们就可以整理

void push_up(int p)
{
    ans[p]=ans[ls(p)]+ans[rs(p)];//整理子节点 
}
void build(int p,int l,int r)//节点编号和左右区间 
{
    tag[p]=0;//标记清空 
    if(l==r)//叶子结点 
    {
        ans[p]=a[l];//赋值 
        return;
    }
    int mid=(l+r)>>1;//二分递归左右儿子 
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
    push_up(p);//整理信息 
}

现在就是区间修改了(单点修改就是区间长为1的特殊的区间修改

这里要引入一个懒标记(lazy

为什么呢,因为如果每次区间修改都递归到叶子结点然后再向上传,这个时间复杂度是极高的

所以当我们找到修改的区间后,打上标记,后面做操作

因为当这个区间的值暂时不用的时候,下一次再修改这个区间,就可以直接累加,两次或者多次操作就可以成功地合并成一次操作,时间复杂度也就大大降低的,但是怎么操作呢?

 

 

 

 

void f(ll p,ll l,ll r,ll k)
{
    tag[p]+=k;//记录标记 
    ans[p]+=k*(r-l+1);//并且更改值 
}

void push_down(ll p,ll l,ll r)
{
    ll mid=(l+r)>>1;
    f(ls(p),l,mid,tag[p]);
    f(rs(p),mid+1,r,tag[p]);
    tag[p]=0;//标记下传,标记清空 
}
void add(int nl,int nr,int l,int r,int p,int k)
{//修改左右区间,当前左右区间,当前节点编号,修改的值 
    if(l>=nl&&r<=nr)//找到区间我们就 
    {
        tag[p]+=k;//直接打标记 
        ans[p]+=k*(r-l+1);//因为区间内每个点都加上k,区间值也就元素个数*k 
        return;
    }
    push_down(p,l,r);//这里有一个向下传递标记的作用,因为能到这里的区间有可能有一部分是要修改的,也就是说对于现在的这个区间
    //是有影响的,,并且后面的查询有可能会用到这个值,所以要更新 
    ll mid=(l+r)>>1;//便利子节点 
    if(nl<=mid)add(nl,nr,l,mid,ls(p),k);
    if(mid1,r,rs(p),k);
    push_up(p);//更新当前结点的信息 
}

为什么修改的时候要下传懒标记并且更新呢

线段树——从入门到入土_第2张图片

 

 当我们看当前区间的时候,要传到子区间,是因为当查询的时候,那一段并没有直接更改值(红色的因为是更改的,所以直接ans变了),为了保证黄色区间的正确性,我们就需要从子节点整合,而子节点恰好需要上面的节点来传递标记更新

而push_down在回溯之前是因为这样才能在向下的时候顺便传递懒标记,而push_up同理,也就是在回溯的时候顺便整理

 

 

询问和修改差不多,也是搜索区间直接返回值

ll answer(ll nl,ll nr,ll l,ll r,ll p)
{
    ll res=0;
    if(nl<=l&&r<=nr)return ans[p];//在区间内直接返回 
    push_down(p,l,r);//以防懒标记未下传,可以想象成这个先answer一步整理数据(因为区间内改可以直接改,不用push_up) 
    ll mid=(l+r)>>1;//左右儿子 
    if(nl<=mid)res+=answer(nl,nr,l,mid,ls(p));
    if(mid1,r,rs(p));
    return res;
}

这个就是线段树的基本操作

 1 #include
 2 #define ll long long
 3 using namespace std;
 4 const int N=1000005<<1;
 5 ll n,m;
 6 ll ans[N];
 7 ll tag[N];
 8 ll a[N];
 9 int ls(int p){return p<<1;}
10 int rs(int p){return p<<1|1;}
11 void push_up(ll p){ans[p]=ans[ls(p)]+ans[rs(p)];}
12 void f(ll p,ll l,ll r,ll k)
13 {
14     tag[p]+=k;
15     ans[p]+=k*(r-l+1);
16 }
17 void push_down(ll p,ll l,ll r)
18 {
19     ll mid=(l+r)>>1;
20     f(ls(p),l,mid,tag[p]);
21     f(rs(p),mid+1,r,tag[p]);
22     tag[p]=0;
23 }
24 void build(ll p,ll l,ll r)
25 {
26     tag[p]=0;
27     if(l==r)
28     {
29         ans[p]=a[l];
30         return;
31     }
32     ll mid=(l+r)>>1;
33     build(ls(p),l,mid);
34     build(rs(p),mid+1,r);
35     push_up(p);
36 }
37 void add(ll nl,ll nr,ll l,ll r,ll p,ll k)
38 {
39     if(l>=nl&&r<=nr)
40     {
41         tag[p]+=k;
42         ans[p]+=k*(r-l+1);
43         return;
44     }
45     push_down(p,l,r);
46     ll mid=(l+r)>>1;
47     if(nl<=mid)add(nl,nr,l,mid,ls(p),k);
48     if(mid1,r,rs(p),k);
49     push_up(p);
50 }
51 ll answer(ll nl,ll nr,ll l,ll r,ll p)
52 {
53     ll res=0;
54     if(nl<=l&&r<=nr)return ans[p];
55     push_down(p,l,r);
56     ll mid=(l+r)>>1;
57     if(nl<=mid)res+=answer(nl,nr,l,mid,ls(p));
58     if(mid1,r,rs(p));
59     return res;
60 }
61 int main()
62 {
63     scanf("%lld%lld",&n,&m);
64     for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
65     build(1,1,n);
66     while(m--)
67     {
68         ll p,l,r,k;
69         scanf("%lld",&p);
70         switch(p)
71         {
72             case 1:{
73                 scanf("%lld%lld%lld",&l,&r,&k);
74                 add(l,r,1,n,1,k);
75                 break;
76             }
77             case 2:{
78                 scanf("%lld%lld",&l,&r);
79                 printf("%lld\n",answer(l,r,1,n,1));
80                 break;
81             }
82         }
83     }
84     return 0;
85 }

 

你可能感兴趣的:(线段树——从入门到入土)