今天我们说说线段树。

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

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

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

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

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

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

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

  •  我的习惯:$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==) t[p].lmax=t[p].rmax=t[p].sum=t[p].r-t[p].l+;
        else t[p].lmax=t[p].rmax=t[p].sum=;
        t[p].lazy=op;//这里也有懒标记更新
        return ;
        }
        int mid=(t[p].l+t[p].r)>>;
        if(l>mid) change(p*+,l,r,op);
        else if(r<=mid) change(p*,l,r,op);//标记判断
        else change(p*,l,mid,op),change(p*+,mid+,r,op);
          //更新
        renew(p);
        }

例题1  【模板】线段树 1

裸的懒标记应用。

 #include<cstdio>
#include<algorithm>
#define maxn 100090 using namespace std;
typedef long long ll; int n,m;
int a[maxn];
struct SegmentTree{
int l,r;
ll lazy,val;
}t[maxn*]; void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
if(l==r)
{
t[p].val=a[l];
return ;
}
int mid=(l+r)>>;
build(p*,l,mid);
build(p*+,mid+,r);
t[p].val=t[p*].val+t[p*+].val;
} void spread(int p)
{
if(t[p].l==t[p].r) return ;
t[p*].val+=t[p].lazy*(t[p*].r-t[p*].l+);
t[p*+].val+=t[p].lazy*(t[p*+].r-t[p*+].l+);
t[p*].lazy+=t[p].lazy;
t[p*+].lazy+=t[p].lazy;
t[p].lazy=;
} void change(int p,int l,int r,int k)
{
spread(p);
if(t[p].l==l&&t[p].r==r)
{
t[p].val+=k*(r-l+);
t[p].lazy+=k;
return ;
}
int mid=(t[p].l+t[p].r)>>;
if(l>mid) change(p*+,l,r,k);
else if(r<=mid) change(p*,l,r,k);
else change(p*,l,mid,k),change(p*+,mid+,r,k);
t[p].val=t[p*].val+t[p*+].val;
} ll ask(int p,int l,int r)
{
spread(p);
if(t[p].l==l&&t[p].r==r) return t[p].val;
int mid=(t[p].l+t[p].r)>>;
if(l>mid) return ask(p*+,l,r);
else if(r<=mid) return ask(p*,l,r);
else return ask(p*,l,mid)+ask(p*+,mid+,r);
} int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++)
scanf("%d",&a[i]);
build(,,n);
for(int i=;i<=m;i++)
{
int opt=;
scanf("%d",&opt);
if(opt==)
{
int x=,y=,k=;
scanf("%d%d%d",&x,&y,&k);
change(,x,y,k);
}
else if(opt==)
{
int x=,y=;
scanf("%d%d",&x,&y);
printf("%lld\n",ask(,x,y));
}
}
return ;
}

例题2 [USACO08FEB]酒店Hotel

By hzwer

题解

线段树

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

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

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

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

$Code$

 #include<cstdio>
#include<algorithm>
#define maxn 50090 using namespace std; int n,m;
struct SegmentTree{
int l,r;
int lazy;
int rmax,lmax,sum;
}t[maxn*]; void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
t[p].lmax=t[p].rmax=t[p].sum=r-l+;
if(l==r) return ;
int mid=(l+r)>>;
build(p*,l,mid);
build(p*+,mid+,r);
} void spread(int p)
{
if(t[p].l==t[p].r) return ;
if(t[p].lazy==)
{
t[p*].lazy=t[p*+].lazy=;
t[p*].lmax=t[p*].rmax=t[p*+].lmax=t[p*+].rmax=;
t[p*].sum=t[p*+].sum=;
}
else if(t[p].lazy==)
{
t[p*].lazy=t[p*+].lazy=;
t[p*].lmax=t[p*].rmax=t[p*].sum=t[p*].r-t[p*].l+;
t[p*+].lmax=t[p*+].rmax=t[p*+].sum=t[p*+].r-t[p*+].l+;
}
t[p].lazy=;
} void renew(int p)
{
if(t[p*].sum==t[p*].r-t[p*].l+)
t[p].lmax=t[p*].r-t[p*].l++t[p*+].lmax;
else t[p].lmax=t[p*].lmax;
if(t[p*+].sum==t[p*+].r-t[p*+].l+)
t[p].rmax=t[p*+].r-t[p*+].l++t[p*].rmax;
else t[p].rmax=t[p*+].rmax;
t[p].sum=max(max(t[p*].sum,t[p*+].sum),t[p*].rmax+t[p*+].lmax);
} 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==) t[p].lmax=t[p].rmax=t[p].sum=t[p].r-t[p].l+;
else t[p].lmax=t[p].rmax=t[p].sum=;
t[p].lazy=op;
return ;
}
int mid=(t[p].l+t[p].r)>>;
if(l>mid) change(p*+,l,r,op);
else if(r<=mid) change(p*,l,r,op);
else change(p*,l,mid,op),change(p*+,mid+,r,op);
renew(p);
} int ask(int p,int len)
{
spread(p);
int mid=(t[p].l+t[p].r)>>;
if(t[p].l==t[p].r) return t[p].l;//找到真正精确的地方了
if(t[p*].sum>=len) return ask(p*,len);
//左面就已经有足够空房 继续向下找更小更精细的
else if(t[p*].rmax+t[p*+].lmax>=len) return mid-t[p*].rmax+;
//跨越边界的部分有足够空房
else return ask(p*+,len);
//否则只能去右子树找连续空房
} int main()
{
scanf("%d%d",&n,&m);
build(,,n);
for(int i=;i<=m;i++)
{
int opt=;
scanf("%d",&opt);
if(opt==)
{
int x=;
scanf("%d",&x);
if(t[].sum<x){printf("0\n");continue;}
int tmp=ask(,x);
printf("%d\n",tmp);
change(,tmp,tmp+x-,);
}
else if(opt==)
{
int x=,y=;
scanf("%d%d",&x,&y);
change(,x,x+y-,);
}
}
return ;
}

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

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

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

 #include<cstdio>
#include<algorithm>
#define maxn 100090 using namespace std;
typedef long long ll; int n,m;
int seq[maxn];
ll moder;
struct SegmentTree{
int l,r;
ll lazy1,lazy2,sum;
}t[maxn*]; void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r,t[p].lazy1=;
if(l==r)
{
t[p].sum=seq[l];
return ;
}
int mid=(l+r)>>;
build(p<<,l,mid);
build(p<<|,mid+,r);
t[p].sum=(t[p<<].sum+t[p<<|].sum)%moder;
} void update(int p)
{
if(!t[p].lazy2&&t[p].lazy1==) return ;
if(t[p].l==t[p].r) return ;
ll add=t[p].lazy2,mul=t[p].lazy1;
(t[p<<].lazy1*=mul)%=moder;
(t[p<<|].lazy1*=mul)%=moder;
(t[p<<].lazy2*=mul)%=moder;
(t[p<<|].lazy2*=mul)%=moder;
(t[p<<].lazy2+=add)%=moder;
(t[p<<|].lazy2+=add)%=moder;
t[p<<].sum=(mul*t[p<<].sum%moder+1ll*add*(t[p<<].r-t[p<<].l+)%moder)%moder;
t[p<<|].sum=(mul*t[p<<|].sum%moder+1ll*add*(t[p<<|].r-t[p<<|].l+)%moder)%moder;
t[p].lazy1=;
t[p].lazy2=;
} void change(int p,int l,int r,ll k,int op)
{
update(p);
if(t[p].l==l&&t[p].r==r)
{
if(op==) (t[p].sum*=k)%=moder,(t[p].lazy1*=k)%=moder,(t[p].lazy2*=k)%moder;
else (t[p].sum+=k*(r-l+))%=moder,(t[p].lazy2+=k)%moder;
return ;
}
int mid=(t[p].l+t[p].r)>>;
if(l>mid) change(p<<|,l,r,k,op);
else if(r<=mid) change(p<<,l,r,k,op);
else change(p<<,l,mid,k,op),change(p<<|,mid+,r,k,op);
t[p].sum=(t[p<<].sum+t[p<<|].sum)%moder;
} ll ask(int p,int l,int r)
{
update(p);
if(t[p].l==l&&t[p].r==r) return t[p].sum;
int mid=(t[p].l+t[p].r)>>;
if(l>mid) return ask(p<<|,l,r);
else if(r<=mid) return ask(p<<,l,r);
else return (ask(p<<,l,mid)%moder+ask(p<<|,mid+,r)%moder)%moder;
} int main()
{
scanf("%d%d%lld",&n,&m,&moder);
for(int i=;i<=n;i++) scanf("%d",&seq[i]);
build(,,n);
for(int i=;i<=m;i++)
{
int op=,x=,y=;ll k=;
scanf("%d",&op);
if(op==)
{
scanf("%d%d%lld",&x,&y,&k);
change(,x,y,k,);
}
else if(op==)
{
scanf("%d%d%lld",&x,&y,&k);
change(,x,y,k,);
}
else if(op==)
{
scanf("%d%d",&x,&y);
printf("%lld\n",ask(,x,y)%moder);
}
}
return ;
}

浅谈线段树 (例题:[USACO08FEB]酒店Hotel)By cellur925的更多相关文章

  1. 【转】Senior Data Structure · 浅谈线段树(Segment Tree)

    本文章转自洛谷 原作者: _皎月半洒花 一.简介线段树 ps: _此处以询问区间和为例.实际上线段树可以处理很多符合结合律的操作.(比如说加法,a[1]+a[2]+a[3]+a[4]=(a[1]+a[ ...

  2. 浅谈线段树 Segment Tree

    众所周知,线段树是algo中很重要的一项! 一.简介 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 使用线段树可以快速的查找某一个节点在 ...

  3. 线段树||BZOJ1593: [Usaco2008 Feb]Hotel 旅馆||Luogu P2894 [USACO08FEB]酒店Hotel

    题面:P2894 [USACO08FEB]酒店Hotel 题解:和基础的线段树操作差别不是很大,就是在传统的线段树基础上多维护一段区间最长的合法前驱(h_),最长合法后驱(t_),一段中最长的合法区间 ...

  4. [USACO08FEB]酒店Hotel 线段树

    [USACO08FEB]酒店Hotel 线段树 题面 其实就是区间多维护一个lmax,rmax(表示从左开始有连续lmax个空房,一直有连续rmax个空房到最右边),合并时讨论一下即可. void p ...

  5. 线段树【洛谷P2894】 [USACO08FEB]酒店Hotel

    P2894 [USACO08FEB]酒店Hotel 参考样例,第一行输入n,m ,n代表有n个房间,编号为1---n,开始都为空房,m表示以下有m行操作,以下 每行先输入一个数 i ,表示一种操作: ...

  6. P2894 [USACO08FEB]酒店Hotel

    P2894 [USACO08FEB]酒店Hotel 简单的线段树维护区间信息. 维护三个值,一个是从左端点能拓展的长度,一个是从右端点能脱产的的长度.另一个是整个区间内的最大连续零一长度. 记录这三个 ...

  7. 洛谷 P2894 [USACO08FEB]酒店Hotel 解题报告

    P2894 [USACO08FEB]酒店Hotel 题目描述 The cows are journeying north to Thunder Bay in Canada to gain cultur ...

  8. 浅谈B+树索引的分裂优化(转)

    http://www.tamabc.com/article/85038.html 从MySQL Bug#67718浅谈B+树索引的分裂优化   原文链接:http://hedengcheng.com/ ...

  9. 浅谈oracle树状结构层级查询之start with ....connect by prior、level及order by

    浅谈oracle树状结构层级查询 oracle树状结构查询即层次递归查询,是sql语句经常用到的,在实际开发中组织结构实现及其层次化实现功能也是经常遇到的,虽然我是一个java程序开发者,我一直觉得只 ...

随机推荐

  1. 第一次OllyDbg逆向记录(分析思路和注意点&其他文章)

    OllyDbg 操作菜单栏.工具栏.快捷键 C++调用加强 目录 OllyDbg 操作菜单栏.工具栏.快捷键    1 一.    载入观察    1 1.静态载入观察:    1 2.OD动态观察  ...

  2. apk程序查找方法调用

    有android killer,现在ida对android的支持等一些方便工具,此篇(关于搜索和修改代码)废弃. 没有好的调试工具下 常用插代码(如果怕影响寄存器值,可以将.locals xxx改多几 ...

  3. Zip加密解密

    Zip加密解密方法: 1.winzipaes http://blog.csdn.net/zhyh1986/article/details/7724229 2.zip4j http://blog.csd ...

  4. HDOJ1006

    #include <cstdio>#include <algorithm>using namespace std;const double UB=43200;const dou ...

  5. Xamarin.Android 实现虾米音乐搜索下载

    之前写过一篇博客,用api接口实现虾米音乐搜索功能,不过这个api接口被封了,所以只能获取到官方的一个音乐json数据,里面的下载地址是被加密过得,这里我会教大家如何用xamarin实现. 准备工作: ...

  6. Handler有何作用?怎样使用?

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u012974916/article/details/24580405 一  Handler作用和概念 ...

  7. python 1: 解决linux系统下python中的matplotlib模块内的pyplot输出图片不能显示中文的问题

    问题: 我在ubuntu14.04下用python中的matplotlib模块内的pyplot输出图片不能显示中文,怎么解决呢? 解决: 1.指定默认编码为UTF-8: 在python代码开头加入如下 ...

  8. (25) java web的struts2框架的使用-基于表单的文件上传

    一,首先创建一个表单页面 <body> <form action="uploads" method="post" enctype=" ...

  9. C/S转分布式数据库的解决方法

    C/S转分布式数据库的解决方法1. 直接VPN建一个网不就行了.(大概是虚拟成一个网络)2. 直连也可以,就是速度慢3. 还是三层吧,推荐RTC4. 弄个花生壳硬件试试呢,成本低,不用改程序5. 搞一 ...

  10. Swift语言学习(三)基础操作符

    操作符是用于检测.更改或者组合值的特殊符号或短语.例如,加法操作符 (+) 将两个数字加到一起 (如 let i = 1 + 2).更复杂的例子包括逻辑与操作符 && (如 if en ...