浅谈线段树 (例题:[USACO08FEB]酒店Hotel)By cellur925
今天我们说说线段树。
我个人还是非常欣赏这种数据结构的。(逃)因为它足够优美,有递归结构,有左子树和右子树,还有二分的思想。
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 ;
}
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的更多相关文章
- 【转】Senior Data Structure · 浅谈线段树(Segment Tree)
本文章转自洛谷 原作者: _皎月半洒花 一.简介线段树 ps: _此处以询问区间和为例.实际上线段树可以处理很多符合结合律的操作.(比如说加法,a[1]+a[2]+a[3]+a[4]=(a[1]+a[ ...
- 浅谈线段树 Segment Tree
众所周知,线段树是algo中很重要的一项! 一.简介 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 使用线段树可以快速的查找某一个节点在 ...
- 线段树||BZOJ1593: [Usaco2008 Feb]Hotel 旅馆||Luogu P2894 [USACO08FEB]酒店Hotel
题面:P2894 [USACO08FEB]酒店Hotel 题解:和基础的线段树操作差别不是很大,就是在传统的线段树基础上多维护一段区间最长的合法前驱(h_),最长合法后驱(t_),一段中最长的合法区间 ...
- [USACO08FEB]酒店Hotel 线段树
[USACO08FEB]酒店Hotel 线段树 题面 其实就是区间多维护一个lmax,rmax(表示从左开始有连续lmax个空房,一直有连续rmax个空房到最右边),合并时讨论一下即可. void p ...
- 线段树【洛谷P2894】 [USACO08FEB]酒店Hotel
P2894 [USACO08FEB]酒店Hotel 参考样例,第一行输入n,m ,n代表有n个房间,编号为1---n,开始都为空房,m表示以下有m行操作,以下 每行先输入一个数 i ,表示一种操作: ...
- P2894 [USACO08FEB]酒店Hotel
P2894 [USACO08FEB]酒店Hotel 简单的线段树维护区间信息. 维护三个值,一个是从左端点能拓展的长度,一个是从右端点能脱产的的长度.另一个是整个区间内的最大连续零一长度. 记录这三个 ...
- 洛谷 P2894 [USACO08FEB]酒店Hotel 解题报告
P2894 [USACO08FEB]酒店Hotel 题目描述 The cows are journeying north to Thunder Bay in Canada to gain cultur ...
- 浅谈B+树索引的分裂优化(转)
http://www.tamabc.com/article/85038.html 从MySQL Bug#67718浅谈B+树索引的分裂优化 原文链接:http://hedengcheng.com/ ...
- 浅谈oracle树状结构层级查询之start with ....connect by prior、level及order by
浅谈oracle树状结构层级查询 oracle树状结构查询即层次递归查询,是sql语句经常用到的,在实际开发中组织结构实现及其层次化实现功能也是经常遇到的,虽然我是一个java程序开发者,我一直觉得只 ...
随机推荐
- [攻防实战]CTF大赛准备(手动注入sql)
一.IIS write漏洞利用 先用工具扫描,再上传小马,使用菜刀连接即可. 思考点: 如何获知是一台IIS站点? 本例中上传的一句话木马是什么意思? <%eval request(" ...
- python day 13 生成器 以及 推导式
1.生成器的本质是迭代器 2.生成器函数 def fn() 函数体 yield fn() g = fn() 此时这个g就是生成器 所以g 是可迭代的 g._ _next_ _ 每执行一次_ _nex ...
- 解决pyspark-linux-windowsIDE JAVA_HOME not set
对 os.environ 赋值 ssh://root@192.168.2.51:22/usr/bin/python -u /home/data/tmp_test/trunk/personas/tmp_ ...
- 时光轴一之listView实现时光轴效果
尼玛.非要搞什么时光轴,一想简单的不就是个listView吗,然后一步一步来就好了,哈哈别看那么好看事实上不要想多了. 时光轴timeline最大的作用就是把过去的事物系统化.完整化.精确化.时间轴可 ...
- oracle case where 复杂sql语句
update hr_user u set u.is_approve=(case when u.curr_org_id in (select t.org_id from hr_organization ...
- vue 使用html2canvas将DOM转化为图片
一.前言 我发现将DOM转化为图片是一个非常常见的需求,而自己手动转是非常麻烦的,于是找到了html2canvas这个插件,既是用得比较多的也是维护得比较好的一个插件. 注意:版本比较多,这里介绍最新 ...
- 织梦CMS被挂马特征汇总
一.织梦CMS被挂马特征汇总 2013织梦CMS被挂马特征汇总.最近很多朋友反应后台多了几个系统管理员用户:service.spider等,而且自己之前的管理员用户登陆时候会提示用户名不存在.还有朋友 ...
- C#方法参数总结
C#中方法的参数的四种类型 C#中方法的参数有四种类型: 1. 值参数类型 (不加任何修饰符,是默认的类型) 2. 引用型参数 (以ref 修饰符声明) 3. ...
- I.MX6 Android /data 目录内容
/**************************************************************************** * I.MX6 Android /data ...
- object_funs.py
#__init__ 构造方法,双下划线 #__del__ 析构方法,在对象就要被垃圾回收前调用.但发生调用 #的具体时间是不可知的.所以建议尽量避免使用__del__ print('-------ex ...