浅谈线段树 (例题:[USACO08FEB]酒店Hotel)By cellur925

今天我们说说线段树。

我个人还是非常欣赏这种数据结构的。(逃)因为它足够优美,有递归结构,有左子树和右子树,还有二分的思想。

emm这个文章打算自用,就不写那些基本的操作了...

1°  简单的懒标记(仅含加法)

当我们进行区间修改(比如同时加上一个数)时,我们现在也许暂时不用它,可以当需要用的时候再改。这个时候我们就需要做个标记,这个标记就是懒标记,$lazy$。如果在后续的指令中需要从p向下递归,我们这时候检查它是否有标记。若有,就按照标记更新两个子节点,同时为子节点增加标记,清除p的标记。

比如最简单的区间修改(加法)

void spread(int p)
{
   if(t[p].l==t[p].r) return ; t[p
*2].val+=t[p].lazy*(t[p*2].r-t[p*2].l+1); t[p*2+1].val+=t[p].lazy*(t[p*2+1].r-t[p*2+1].l+1); //标记释放威力 t[p*2].lazy+=t[p].lazy; t[p*2+1].lazy+=t[p].lazy; //标记下传 t[p].lazy=0; //清空自身标记 }

也就是说,其实标记是为自己的儿子们准备的,而自己已经修改了。当自己的儿子接手了标记的衣钵,父亲也就不需要存在标记的。

  •  我的习惯:$spread$常写,函数内判断条件苛刻。
  •     一个习惯的change函数写法:
      •  
        void change(int p,int l,int r,int op)
        {//op==1 I have rooms!
         //op==2 I lose rooms!
             spread(p); //标记下放
            if(t[p].l==l&&t[p].r==r)//边界判断
            {
                if(op==1) t[p].lmax=t[p].rmax=t[p].sum=t[p].r-t[p].l+1;
                else t[p].lmax=t[p].rmax=t[p].sum=0;
                t[p].lazy=op;//这里也有懒标记更新
                return ;
            }
            int mid=(t[p].l+t[p].r)>>1;
            if(l>mid) change(p*2+1,l,r,op);
            else if(r<=mid) change(p*2,l,r,op);//标记判断
            else change(p*2,l,mid,op),change(p*2+1,mid+1,r,op);
          //更新 renew(p); }

例题1  【模板】线段树 1

裸的懒标记应用。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define maxn 100090
 4 
 5 using namespace std;
 6 typedef long long ll;
 7 
 8 int n,m;
 9 int a[maxn];
10 struct SegmentTree{
11     int l,r;
12     ll lazy,val;
13 }t[maxn*4];
14 
15 void build(int p,int l,int r)
16 {
17     t[p].l=l,t[p].r=r;
18     if(l==r)
19     {
20         t[p].val=a[l];
21         return ;
22     }
23     int mid=(l+r)>>1;
24     build(p*2,l,mid);
25     build(p*2+1,mid+1,r);
26     t[p].val=t[p*2].val+t[p*2+1].val;
27 }
28 
29 void spread(int p)
30 {
31     if(t[p].l==t[p].r) return ;
32     t[p*2].val+=t[p].lazy*(t[p*2].r-t[p*2].l+1);
33     t[p*2+1].val+=t[p].lazy*(t[p*2+1].r-t[p*2+1].l+1);
34     t[p*2].lazy+=t[p].lazy;
35     t[p*2+1].lazy+=t[p].lazy;
36     t[p].lazy=0;
37 }
38 
39 void change(int p,int l,int r,int k)
40 {
41     spread(p);
42     if(t[p].l==l&&t[p].r==r)
43     {
44         t[p].val+=k*(r-l+1);
45         t[p].lazy+=k;
46         return ;
47     }
48     int mid=(t[p].l+t[p].r)>>1;
49     if(l>mid) change(p*2+1,l,r,k);
50     else if(r<=mid) change(p*2,l,r,k);
51     else change(p*2,l,mid,k),change(p*2+1,mid+1,r,k);
52     t[p].val=t[p*2].val+t[p*2+1].val;
53 }
54 
55 ll ask(int p,int l,int r)
56 {
57     spread(p);
58     if(t[p].l==l&&t[p].r==r) return t[p].val;
59     int mid=(t[p].l+t[p].r)>>1;
60     if(l>mid) return ask(p*2+1,l,r);
61     else if(r<=mid) return ask(p*2,l,r);
62     else return ask(p*2,l,mid)+ask(p*2+1,mid+1,r); 
63 }
64 
65 int main()
66 {
67     scanf("%d%d",&n,&m);
68     for(int i=1;i<=n;i++)
69         scanf("%d",&a[i]);
70     build(1,1,n);
71     for(int i=1;i<=m;i++)
72     {
73         int opt=0;
74         scanf("%d",&opt);
75         if(opt==1)
76         {
77             int x=0,y=0,k=0;
78             scanf("%d%d%d",&x,&y,&k);
79             change(1,x,y,k);
80         }
81         else if(opt==2)
82         {
83             int x=0,y=0;
84             scanf("%d%d",&x,&y);
85             printf("%lld
",ask(1,x,y));
86         }
87     }
88     return 0;
89 }
View Code

例题2 [USACO08FEB]酒店Hotel 

By hzwer

题解

线段树

每个节点记录该段最长连续长度

为了合并还要记录坐标开始的连续长度,右边开始的连续长度

这里用到了线段树中另一个常见的思想。平常我们用线段树大多都是在维护一个值,而遇到一些复杂的信息需要维护时,我们就很难纯粹地加加减减,那么我们不妨换一种思路,多维护一些信息。最早应用这个思想的是最大子段和的维护,详情。(当时我还在$tsoi$讲过内qwq)就是多维护了$lmax$,$rmax$。这样父亲线段的最值可以由左儿子的$val$、右儿子的$val$、左儿子的$rmax$加右儿子的$lmax$更新维护来。

那么回到本题:一句话题意就是在维护,最大连续空房。综合之前分析,知道如何用懒标记还有知道需要维护哪些信息后,这道题就比较简单了。

$Code$

  1 #include<cstdio>
  2 #include<algorithm>
  3 #define maxn 50090
  4 
  5 using namespace std;
  6 
  7 int n,m;
  8 struct SegmentTree{
  9     int l,r;
 10     int lazy;
 11     int rmax,lmax,sum;
 12 }t[maxn*4];
 13 
 14 void build(int p,int l,int r)
 15 {
 16     t[p].l=l,t[p].r=r;
 17     t[p].lmax=t[p].rmax=t[p].sum=r-l+1;
 18     if(l==r) return ;
 19     int mid=(l+r)>>1;
 20     build(p*2,l,mid);
 21     build(p*2+1,mid+1,r);
 22 }
 23 
 24 void spread(int p)
 25 {
 26     if(t[p].l==t[p].r) return ; 
 27     if(t[p].lazy==2)
 28     {
 29         t[p*2].lazy=t[p*2+1].lazy=2;
 30         t[p*2].lmax=t[p*2].rmax=t[p*2+1].lmax=t[p*2+1].rmax=0;
 31         t[p*2].sum=t[p*2+1].sum=0;
 32     }
 33     else if(t[p].lazy==1)
 34     {
 35         t[p*2].lazy=t[p*2+1].lazy=1;
 36         t[p*2].lmax=t[p*2].rmax=t[p*2].sum=t[p*2].r-t[p*2].l+1;
 37         t[p*2+1].lmax=t[p*2+1].rmax=t[p*2+1].sum=t[p*2+1].r-t[p*2+1].l+1;
 38     }
 39     t[p].lazy=0;
 40 }
 41 
 42 void renew(int p)
 43 {
 44     if(t[p*2].sum==t[p*2].r-t[p*2].l+1)
 45         t[p].lmax=t[p*2].r-t[p*2].l+1+t[p*2+1].lmax;
 46     else t[p].lmax=t[p*2].lmax;
 47     if(t[p*2+1].sum==t[p*2+1].r-t[p*2+1].l+1)
 48         t[p].rmax=t[p*2+1].r-t[p*2+1].l+1+t[p*2].rmax;
 49     else t[p].rmax=t[p*2+1].rmax;
 50     t[p].sum=max(max(t[p*2].sum,t[p*2+1].sum),t[p*2].rmax+t[p*2+1].lmax);
 51 }
 52 
 53 void change(int p,int l,int r,int op)
 54 {//op==1 I have rooms!
 55  //op==2 I lose rooms!
 56      spread(p); 
 57     if(t[p].l==l&&t[p].r==r)
 58     {
 59         if(op==1) t[p].lmax=t[p].rmax=t[p].sum=t[p].r-t[p].l+1;
 60         else t[p].lmax=t[p].rmax=t[p].sum=0;
 61         t[p].lazy=op;
 62         return ;
 63     }
 64     int mid=(t[p].l+t[p].r)>>1;
 65     if(l>mid) change(p*2+1,l,r,op);
 66     else if(r<=mid) change(p*2,l,r,op);
 67     else change(p*2,l,mid,op),change(p*2+1,mid+1,r,op);
 68     renew(p);
 69 }
 70 
 71 int ask(int p,int len)
 72 {
 73     spread(p);
 74     int mid=(t[p].l+t[p].r)>>1;
 75     if(t[p].l==t[p].r) return t[p].l;//找到真正精确的地方了 
 76     if(t[p*2].sum>=len) return ask(p*2,len);
 77     //左面就已经有足够空房 继续向下找更小更精细的 
 78     else if(t[p*2].rmax+t[p*2+1].lmax>=len) return mid-t[p*2].rmax+1;
 79     //跨越边界的部分有足够空房 
 80     else return    ask(p*2+1,len);
 81     //否则只能去右子树找连续空房 
 82 }
 83 
 84 int main()
 85 {
 86     scanf("%d%d",&n,&m);
 87     build(1,1,n);
 88     for(int i=1;i<=m;i++)
 89     {
 90         int opt=0;
 91         scanf("%d",&opt);
 92         if(opt==1)
 93         {
 94             int x=0;
 95             scanf("%d",&x);
 96             if(t[1].sum<x){printf("0
");continue;}
 97             int tmp=ask(1,x);
 98             printf("%d
",tmp);
 99             change(1,tmp,tmp+x-1,2);
100         }
101         else if(opt==2)
102         {
103             int x=0,y=0;
104             scanf("%d%d",&x,&y);
105             change(1,x,x+y-1,1);
106         } 
107     }
108     return 0;
109 }
View Code

Update:话说最近做了不少(?)线段树,有一种感觉十分友好,就是那种操作一定数量后操作失效的(如开方),那么我们可以记录一个区间最大值来检验是否还需要操作,思想很妙。

还有:线段树这种用左儿子+右儿子+左右儿子交界来更新答案的这种思想,最初是在维护最大子段和看到的。

Update:同时维护区间乘法&区间加法?再加一个懒标记记录乘法!要注意的是区间乘法修改时加法懒标记也要乘上修改值,$update$时加法懒标记也要乘上修改值,也就是加法一直在听着乘法的话。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define maxn 100090
 4 
 5 using namespace std;
 6 typedef long long ll;
 7 
 8 int n,m;
 9 int seq[maxn];
10 ll moder;
11 struct SegmentTree{
12     int l,r;
13     ll lazy1,lazy2,sum;
14 }t[maxn*4];
15 
16 void build(int p,int l,int r)
17 {
18     t[p].l=l,t[p].r=r,t[p].lazy1=1;
19     if(l==r)
20     {
21         t[p].sum=seq[l];
22         return ;
23     }
24     int mid=(l+r)>>1;
25     build(p<<1,l,mid);
26     build(p<<1|1,mid+1,r);
27     t[p].sum=(t[p<<1].sum+t[p<<1|1].sum)%moder;
28 }
29 
30 void update(int p)
31 {
32     if(!t[p].lazy2&&t[p].lazy1==1) return ;
33     if(t[p].l==t[p].r) return ;
34     ll add=t[p].lazy2,mul=t[p].lazy1;
35     (t[p<<1].lazy1*=mul)%=moder;
36     (t[p<<1|1].lazy1*=mul)%=moder;
37     (t[p<<1].lazy2*=mul)%=moder;
38     (t[p<<1|1].lazy2*=mul)%=moder;
39     (t[p<<1].lazy2+=add)%=moder;
40     (t[p<<1|1].lazy2+=add)%=moder;
41     t[p<<1].sum=(mul*t[p<<1].sum%moder+1ll*add*(t[p<<1].r-t[p<<1].l+1)%moder)%moder;
42     t[p<<1|1].sum=(mul*t[p<<1|1].sum%moder+1ll*add*(t[p<<1|1].r-t[p<<1|1].l+1)%moder)%moder;
43     t[p].lazy1=1;
44     t[p].lazy2=0;
45 }
46 
47 void change(int p,int l,int r,ll k,int op)
48 {
49     update(p);
50     if(t[p].l==l&&t[p].r==r)
51     {
52         if(op==1) (t[p].sum*=k)%=moder,(t[p].lazy1*=k)%=moder,(t[p].lazy2*=k)%moder;
53         else (t[p].sum+=k*(r-l+1))%=moder,(t[p].lazy2+=k)%moder;
54         return ;
55     }
56     int mid=(t[p].l+t[p].r)>>1;
57     if(l>mid) change(p<<1|1,l,r,k,op);
58     else if(r<=mid) change(p<<1,l,r,k,op);
59     else change(p<<1,l,mid,k,op),change(p<<1|1,mid+1,r,k,op);
60     t[p].sum=(t[p<<1].sum+t[p<<1|1].sum)%moder;  
61 }
62 
63 ll ask(int p,int l,int r)
64 {
65     update(p);
66     if(t[p].l==l&&t[p].r==r) return t[p].sum;
67     int mid=(t[p].l+t[p].r)>>1;
68     if(l>mid) return ask(p<<1|1,l,r);
69     else if(r<=mid) return ask(p<<1,l,r);
70     else return (ask(p<<1,l,mid)%moder+ask(p<<1|1,mid+1,r)%moder)%moder;
71 }
72 
73 int main()
74 {
75     scanf("%d%d%lld",&n,&m,&moder);
76     for(int i=1;i<=n;i++) scanf("%d",&seq[i]);
77     build(1,1,n);
78     for(int i=1;i<=m;i++)
79     {
80         int op=0,x=0,y=0;ll k=0;
81         scanf("%d",&op);
82         if(op==1)
83         {
84             scanf("%d%d%lld",&x,&y,&k);
85             change(1,x,y,k,1);
86         }
87         else if(op==2)
88         {
89             scanf("%d%d%lld",&x,&y,&k);
90             change(1,x,y,k,2);
91         }
92         else if(op==3)
93         {
94             scanf("%d%d",&x,&y);
95             printf("%lld
",ask(1,x,y)%moder);
96         }
97     }    
98     return 0;
99 }
View Code
原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9735485.html