• 浅谈线段树 (例题:[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
  • 相关阅读:
    获取全部校园新闻
    爬取校园新闻首页的新闻的详情,使用正则表达式,函数抽离
    网络爬虫基础练习
    编写可维护的JavaScript
    markDown使用
    好文推荐系列---------JS模板引擎
    backbone框架思路
    如何获取URL中的参数
    如何使用NodeJs来监听文件变化
    sass基本用法(转载)
  • 原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9735485.html
Copyright © 2020-2023  润新知