今天我们说说线段树。

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

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. PJzhang:python基础入门的7个疗程-two

    猫宁!!! 参考链接:易灵微课-21天轻松掌握零基础python入门必修课-售价29元人民币 https://www.liaoxuefeng.com/wiki/1016959663602400 htt ...

  2. 网页JS简繁体字转换

    用法:非得加上html头 utf-8编码 其它编码无测试 head 中引用 <script language='javascript' src='zh.js'></script> ...

  3. 【转载】C#相等性比较

    本文阐述C#中相等性比较,其中主要集中在下面两个方面 ==和!=运算符,什么时候它们可以用于相等性比较,什么时候它们不适用,如果不使用,那么它们的替代方式是什么? 什么时候,需要自定一个类型的相等性比 ...

  4. 高速查询hive数据仓库表中的总条数

    Author: kwu 高速查询hive数据仓库中的条数.在查询hive表的条数,通常使用count(*).可是数据量大的时候,mr跑count(*)往往须要几分钟的时间. 1.传统方式获得总条数例如 ...

  5. VB.net版机房收费系统——结账功能实现(调错与优化)

    调错部分 上一篇博客<VB.net版机房收费系统--结账功能实现(代码部分>说的是结账功能的实现,亮出了代码.是在为这篇博客做铺垫.尽管结账功能代码是借鉴的巨人的博客.可是自己比着葫芦画瓢 ...

  6. button在firefox 和 ie 下的问题

    最近做了一个关于数据库管理的项目,因为不用考虑ie9以下的兼容性,所以一股脑的写完啦,到测试的时候发现了一个bug IE和火狐下有个模块关闭按钮的hover没有反应,ie不行就算了,火狐怎么也不行?我 ...

  7. 数据库DCL、DDL、DML、DQL

    SQL三部分:data manipulation language      DCL: (控制)管理用户权限(GRANT.REVOKE),数据库整体配置      DDL: (定义)作用于数据库,表, ...

  8. (linux)块设备驱动程序

      1.4.1  Linux块设备驱动程序原理(1) 顾名思义,块设备驱动程序就是支持以块的方式进行读写的设备.块设备和字符设备最大的区别在于读写数据的基本单元不同.块设备读写数据的基本单元为块,例如 ...

  9. NOIP2016总结

    Day1: T1:模拟: #include<iostream> #include<cstdio> #include<cstdlib> #include<cst ...

  10. 一步一步学Silverlight 2系列(19):如何在Silverlight中与HTML DOM交互(上)

    概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...