算法学习:线段树

线段树,个人理解,生成一棵二叉树,树上的节点表示区间的答案,因为二叉树的性质天然就将树分成两半,所以可以用每个节点存左半边右半边,然后这样子就可以保证效率。

具体讲解是看这位大大的博客,图解和语言都很详细。

https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html

这个里面树是靠结构体存储了一个真正意义上节点为包含左右区间范围及其答案的节点

然后我的代码是参考了洛谷的一个题解的代码,所以是用询问函数上的范围取代了,但是这两者区别并不是很大,因为p这个节点的数字,通过二进制来看,天然的就可以求取出他所代表的区间。

附代码,对树的解释都在注释上:

  1 #include<cstdio>
  2 #define ll long long
  3 #define MAXN 100010
  4 int a[MAXN];
  5 int ans[MAXN];
  6 int t[MAXN];
  7 int tag[MAXN];
  8 inline int ls(int p){return p<<1;}//找左节点 
  9 inline int rs(int p){return p<<1|1;}//找右节点 
 10 void push_up(int p)//更新操作 
 11 {
 12     ans[p]=ans[ls(p)]+ans[rs(p)];
 13     return;
 14 }
 15 void build(ll p,ll l,ll r)//建树 
 16 {
 17     if(l==r)//如果是底层叶节点 
 18     {        //l==r区间,也就是这个节点本身 
 19         ans[p]=a[l];    //返回叶节点 
 20         return;            //那么就是当前数组保存此节点 
 21     }
 22     ll mid=(l+r)>>1;
 23     build(ls(p),l,mid);//使p的左孩子包含(l---mid)的值 
 24     build(rs(p),mid+1,r);
 25     push_up(p); 
 26     //左右结点的值已经通过递归得到
 27     //可以根据左右确定(更新)自己的值 
 28     return;
 29 }
 30 void f(int p,int l,int r,int k)
 31 {
 32     tag[p]+=k;
 33     //给当前节点加上之前节点的懒标记 
 34     //这样子懒标记不会下传
 35     //但是之后还可以用,不会影响其他的查询 
 36     ans[p]+=(r+1-l)*k;
 37     //更新树上的值
 38     //这样就不会向下扩散 
 39     //保证效率 
 40 }
 41 void push_down(int p,int l,int r)
 42 {
 43     ll mid=(l+r)>>1;
 44     //向下更新 
 45     f(ls(p),l,mid,tag[p]);
 46     f(rs(p),mid+1,r,tag[p]);
 47     tag[p]=0;
 48     //因为这个点的懒标记已经下传
 49     //所以这个点对下面来说
 50     //已经没有了需要更新的值 
 51     //懒标记清0 
 52 } 
 53 //我个人最难理解的一步 
 54 //对区间的更新 
 55 //nr,nl需要查询的期间
 56 //l,r当前节点 
 57 void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
 58 {
 59     if(nl<=l&&r<=nr)
 60     //若当前区间全部在所查询区间
 61     //直接进行更新 
 62         {
 63             ans[p]+=(r+1-l)*k;
 64             tag[p]+=k;
 65             return ;     
 66         }    
 67     push_down(p,l,r); 
 68     //因为需要保证下面两个节点的正确性 
 69     //所以要向下进行更新 
 70     ll mid=(r+l)>>1;
 71     if(nl<=mid)    update(nl,nr,l,mid,ls(p),k);
 72     if(nr>mid)    update(nl,nr,mid+1,r,rs(p),k);
 73     //根据区间对子节点进行更新 
 74     //先将子节点更新完成 
 75     push_up(p);
 76     return;
 77 } 
 78 ll query(ll nl,ll nr,ll l,ll r,ll p)
 79 {
 80     ll res=0;
 81     if(nl<=l&&r<=nr)
 82         {
 83             return ans[p];     
 84         }    
 85     ll mid=(r+l)>>1;
 86     push_down(p,l,r);
 87     if(nl<=mid)    res+=query(nl,nr,l,mid,ls(p));
 88     if(nr>mid)    res+=query(nl,nr,mid+1,r,rs(p));
 89     return    res;
 90 }
 91 int main()
 92 {
 93     int n,m;
 94     scanf("%d%d",&n,&m);
 95     for(int i=1;i<=n;i++)
 96         {
 97             scanf("%d",&a[i]);
 98         }
 99     build(1,1,n);
100     while(m--)
101     {
102         int p;
103         scanf("%d",&p);
104         switch(p)
105         {
106             case 1:
107                 {
108                     int l,r,k;
109                     scanf("%d%d%d",&l,&r,&k);
110                     update(l,r,1,n,1,k);
111                     //可以理解为对第一个节点进行更新
112                     //第一个节点包括的区间就是1~n 
113                     break;                //修改操作 
114                 }
115             case 2:
116                 {
117                     int l,r;
118                     scanf("%d%d",&l,&r);
119                     printf("%lld\n",query(l,r,1,n,1));//输出值的操作 
120                     break;//1,n代表的是第一个节点,代表的是具有从1~n的值的数字 
121                 }
122         }
123     }
124     return 0;
125 }
注释版
  1 #include<cstdio>
  2 #define ll long long
  3 #define MAXN 100010
  4 int a[MAXN];
  5 int ans[MAXN];
  6 int t[MAXN];
  7 int tag[MAXN];
  8 inline int ls(int p){return p<<1;}
  9 inline int rs(int p){return p<<1|1;} 
 10 void push_up(int p)
 11 {
 12     ans[p]=ans[ls(p)]+ans[rs(p)];
 13     return;
 14 }
 15 void build(ll p,ll l,ll r)
 16 {
 17     if(l==r)    
 18     {         
 19         ans[p]=a[l];    
 20         return;             
 21     }
 22     ll mid=(l+r)>>1;
 23     build(ls(p),l,mid); 
 24     build(rs(p),mid+1,r);
 25     push_up(p); 
 26     return;
 27 }
 28 void f(int p,int l,int r,int k)
 29 {
 30     tag[p]+=k;
 31     ans[p]+=(r+1-l)*k;
 32 }
 33 void push_down(int p,int l,int r)
 34 {
 35     ll mid=(l+r)>>1;
 36     f(ls(p),l,mid,tag[p]);
 37     f(rs(p),mid+1,r,tag[p]);
 38     tag[p]=0;
 39 } 
 40 void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
 41 {
 42     push_down(p,l,r); 
 43     if(nl<=l&&r<=nr) 
 44         {
 45             ans[p]+=(r+1-l)*k;
 46             tag[p]+=k;
 47             return ;     
 48         }    
 49     ll mid=(r+l)>>1;
 50     if(nl<=mid)    update(nl,nr,l,mid,ls(p),k);
 51     if(nr>mid)    update(nl,nr,mid+1,r,rs(p),k);
 52     push_up(p);
 53     return;
 54 } 
 55 ll query(ll nl,ll nr,ll l,ll r,ll p)
 56 {
 57     ll res=0;
 58     push_down(p,l,r); 
 59     if(nl<=l&&r<=nr)
 60         {
 61             return ans[p];     
 62         }    
 63     ll mid=(r+l)>>1;
 64     if(nl<=mid)    res+=query(nl,nr,l,mid,ls(p));
 65     if(nr>mid)    res+=query(nl,nr,mid+1,r,rs(p));
 66     return    res;
 67 }
 68 int main()
 69 {
 70     int n,m;
 71     scanf("%d%d",&n,&m);
 72     for(int i=1;i<=n;i++)
 73         {
 74             scanf("%d",&a[i]);
 75         }
 76     build(1,1,n);
 77     while(m--)
 78     {
 79         int p;
 80         scanf("%d",&p);
 81         switch(p)
 82         {
 83             case 1:
 84                 {
 85                     int l,r,k;
 86                     scanf("%d%d%d",&l,&r,&k);
 87                     update(l,r,1,n,1,k);
 88                     break;            
 89                 }
 90             case 2:
 91                 {
 92                     int l,r;
 93                     scanf("%d%d",&l,&r);
 94                     printf("%lld\n",query(l,r,1,n,1));
 95                     break; 
 96                 }
 97         }
 98     }
 99     return 0;
100 }
101 

无注释版


 

例题:

洛谷P1198【JSOI 2008】最大的数

【题意】两种操作:

               1:查询从末尾倒数L个数中最大值

               2:将输入数n加上上次的查询值(上次没有查询+0)插入末尾

【思路】1:查询直接在已有线段树查询

               2:插入,将整棵线段树视作所有值都为0的树,插入时视作,改变size(整棵树中元素个数)+1的位置加上这个值,然后线段树操作在1~Mmax上更新

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<algorithm>
 4 #define MAXN 200007
 5 #define MINN (-1)*((1<<31)-1)
 6 using namespace std;
 7 int tree[MAXN<<2];
 8 int mod,t=0;
 9 int n=0;
10 void init()
11 {
12     for(int i=1;i<MAXN;i++)
13         {
14              tree[i]=MINN;
15         }
16     return;
17 }
18 void push_up(int p)
19 {
20     int maxn=max(tree[p<<1],tree[p<<1|1]);
21     tree[p]=max(maxn,tree[p]);
22     return ;
23 }
24 int add(int rt,int l,int r,int p,int val)
25 {
26     if(l==r)
27         {
28             tree[rt]=val;
29             return 0;
30         }
31     int mid=(l+r)>>1;
32     if(p<=mid)
33         add(rt<<1,l,mid,p,val);
34     else
35         add(rt<<1|1,mid+1,r,p,val);
36     push_up(rt);
37 }
38 int quer(int nl,int nr,int l,int r,int p)
39 {
40     if(nl<=l&&r<=nr)
41         {
42             return tree[p];
43         }
44     int res=0;
45     int mid=(l+r)>>1;
46     if(nl<=mid)    
47         res=quer(nl,nr,l,mid,p<<1);        
48     if(nr>mid)
49         res=max(res,quer(nl,nr,mid+1,r,p<<1|1));
50     return res;
51 }
52 int main()
53 {
54      int m;
55      scanf("%d%d",&m,&mod);
56      init();
57      getchar();
58      getchar();
59      for(int i=1;i<=m;i++)
60          {
61              char c;
62             int d;
63             scanf("%c %d",&c,&d);
64             getchar();
65             getchar();
66             int tmp=(t+d)%mod;
67             //printf("tmp=%d\n",tmp);
68             switch(c)     
69                 {
70                     case 'A':
71                         n++;
72                         add(1,1,m,n,tmp);
73                         break;
74                     case 'Q':
75                         t=(quer(n+1-d,n,1,m,1))%mod;
76                         printf("%d\n",t);        
77                         break;
78                 }
79         }
80     return 0;
81 }
P1198

洛谷P2023 [AHOI2009] 维护序列

【题意】两种操作:
               1:给区间加一个数

               2:给区间乘一个数

               3:询问区间和

【思路】线段树常用方法,开两个懒标记,一个储存乘,一个储存加,在乘下传的时候考虑乘标记对加标记的影响

#include<cstdio>
#include<iostream>
#define ll long long
#define MAXN 100010
using namespace std;
ll a[MAXN];
ll ans[MAXN<<2];
ll res;
ll tag_m[MAXN<<2],tag_a[MAXN<<2];
ll mod,n,m;
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;} 
void push_up(int p)
{
    ans[p]=(ans[ls(p)]+ans[rs(p)])%mod;
    return;
}
void build(ll p,ll l,ll r)
{
    if(l==r)    
    {         
        ans[p]=a[l];    
        return;             
    }
    ll mid=(l+r)>>1;
    build(ls(p),l,mid); 
    build(rs(p),mid+1,r);
    push_up(p); 
    return;
}
void push_down(int p,int l,int r,int op)
{
    if(tag_m[p]==1&&tag_a[p]==0)
        return;
    ll lp=ls(p),rp=rs(p);
    if(l!=r)
        {
            tag_m[rp]=tag_m[rp]*tag_m[p]%mod;
            tag_m[lp]=tag_m[lp]*tag_m[p]%mod;
            tag_a[lp]=((tag_a[lp]*tag_m[p])%mod+tag_a[p])%mod;
            tag_a[rp]=((tag_a[rp]*tag_m[p])%mod+tag_a[p])%mod;
        }    
    ans[p]=(ans[p]*tag_m[p]%mod+tag_a[p]*(r-l+1)%mod)%mod;
    tag_m[p]=1;tag_a[p]=0;
} 
void update(ll nl,ll nr,ll l,ll r,ll p,ll k,int op)
{    
    push_down(p,l,r,op);
    if(nl<=l&&r<=nr) 
        {
            switch(op)
                {
                    case 1:tag_m[p]=(tag_m[p]*k)%mod,tag_a[p]=(tag_a[p]*k)%mod;break;
                    case 2:tag_a[p]=(tag_a[p]+k)%mod;break;
                }    
            return ;     
        }    
    ll mid=(r+l)>>1;
    if(nl<=mid)    update(nl,nr,l,mid,ls(p),k,op);
    if(nr>mid)    update(nl,nr,mid+1,r,rs(p),k,op);
    push_down(ls(p),l,mid,op);
    push_down(rs(p),mid+1,r,op);
    push_up(p);
    return;
} 
ll query(ll nl,ll nr,ll l,ll r,ll p,int op)
{
    ll res=0;
    push_down(p,l,r,op);
    if(nl<=l&&r<=nr)
        {
            return ans[p];     
        }    
    ll mid=(r+l)>>1;    
    if(nl<=mid)    res+=query(nl,nr,l,mid,ls(p),op);
    if(nr>mid)    res+=query(nl,nr,mid+1,r,rs(p),op);
    return    res;
}
int main()
{
    scanf("%lld%lld",&n,&mod);
    for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
        }
    for(int i=1;i<=4*n;i++)
        {
            tag_m[i]=1;
        }
    build(1,1,n);
    scanf("%lld",&m);
    for(int i=1;i<=m;i++)    
        {
            int op,l,r,k;
            scanf("%d",&op);
            switch(op)
                {
                
                    case 1:
                    case 2:scanf("%d%d%d",&l,&r,&k);
                           update(l,r,1,n,1,k,op);
                           break;
                    case 3:scanf("%d%d",&l,&r);
                          res=query(l,r,1,n,1,op)%mod;
                           printf("%lld\n",res);
                          break;
                }
        }
}
P2023

 

posted @ 2019-07-04 19:19  rentu  阅读(295)  评论(0编辑  收藏  举报