今天我们说说线段树。

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

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. Hadoop教程(一)

    英文原文:cloudera,编译:ImportNew – Royce Wong Hadoop从这里开始!和我一起学习下使用Hadoop的基本知识,下文将以Hadoop Tutorial为主体带大家走一 ...

  2. XML Schema笔记

    XML Schema是为了弥补DTD的不足而开发的一种新的用于约束和规范XML文档的标准 XML Schema作用: 定义可出现在文档中的元素定义可出现在文档中的属性定义哪些元素是子元素定义子元素的次 ...

  3. AdaBoost算法原理简介

    AdaBoost算法原理 AdaBoost算法针对不同的训练集训练同一个基本分类器(弱分类器),然后把这些在不同训练集上得到的分类器集合起来,构成一个更强的最终的分类器(强分类器).理论证明,只要每个 ...

  4. adb4robotium跨进程框架抛出InputStream cannot be null的异常的解决方案

    转自:http://blog.csdn.net/qingchunjun/article/details/43448371 之前我写的关于利用adb框架来进行robotium跨进程操作的文章中,有些朋友 ...

  5. Cocos2d-js异步图片加载

    这里说的是在需要的使用加载图片,比如游戏中的某个关卡的图片,不用在游戏一开始就加载(万一用户玩不到那关,岂不是很冤,流量费了那么多),否则 载入速度也慢.这种方式加载资源要用到cc.loader官方文 ...

  6. PHP获取类名及所有函数名

    PHP获取当前类名.方法名  __CLASS__ 获取当前类名  __FUNCTION__ 当前函数名(confirm)  __METHOD__ 当前方法名 (bankcard::confirm) _ ...

  7. jquery和CSS3带倒影的3D万花筒旋转动画特效效果演示

    <!DOCTYPE html> <html> <head> <title></title> <meta charset='utf-8' ...

  8. css3某些特性

    在下列情况下,建议使用opacity属性而不是rgba()函数 1.实现多种颜色(元素)的半透明效果.使用opacity属性,不仅背景颜色,就连文本颜色.边框颜色都会变透明. 2.在不知道颜色的情况下 ...

  9. chan

    第一单元:分型.笔.线段 ?1  分型 一.分型.笔和线段所属范畴 缠师在<教你炒股票72:本ID已有课程的再梳理>中对缠论做过这样的说明“本ID的理论,本质上分两部分,一是形态学,二是动 ...

  10. 软件GUI测试中的关注点

    [摘要] 本文列数了软件黑盒测试过程中,在被测试软件中可能存在的常见软件问题.本文不会详细讨论基本的软件测试思想与常用技术,仅针对在软件黑盒测试过程中若干的问题做描述,并提供个人的参考测试意见与防范意 ...