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

Posted nopartyfoucaodong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈线段树 (例题:[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

 

未完待续.



以上是关于浅谈线段树 (例题:[USACO08FEB]酒店Hotel)By cellur925的主要内容,如果未能解决你的问题,请参考以下文章

[USACO08FEB] 酒店Hotel - 线段树

P2894 [USACO08FEB]酒店Hotel

luogu2894 [USACO08FEB]酒店Hotel

[USACO08FEB]酒店Hotel

poj3667(bzoj1593)--Usaco08Feb Hotel--线段树区间合并

[bzoj3939_Usaco2015 Feb]Cow Hopscotch(线段树维护DP)