Pascal 线段树 lazy-tag 模板
先说下我的代码风格(很丑,勿喷)
maxn表示最大空间的四倍
tree数组表示求和的线段树
delta表示增减的增量标记
sign表示覆盖的标记
delta,sign实际上都是lazy标志
pushdown表示标记下传
pushup表示标记上传(即求和,区间最值)
update表示数据更新
线段树(segment tree)是一种特别有用的数据结构,我们在维护区间各种信息的时候它就是利器。可能读者嫌线段树代码太长,不想写,而树状数组代码简洁易于便携,但是我在这里想说,线段树能做到的很多东西树状数组无法做到,而若掌握了线段树的基本框架,再去写熟练的话,基本上能够做到较短的时间内DEBUG完。
我们来介绍一下什么是线段树。
问题1:
我们拥有一个数列,需要维护三个操作
1.单点修改,把a[i]的值改成v
2.区间修改,把a[i]~a[j]的值改成v
3.区间求和,求出a[i]~a[j]的和
我们不难想到使用数组存储这些东西,对于每个询问我们暴力修改,可这样的话,对于每一段区间,我们需要耗费的时间是r-l+1,当Q特别大、或者区间长度特别大的时候,时间无法忍受。这时候线段树的应用便出来了。
何谓线段树?我们把线段存储在树形结构中,利用树形结构维护线段信息的数据结构,就是线段树。
我们利用二分建树,建立一颗二叉树。为什么是二叉树呢?我个人理解是,这方便我们二分查找,二分递归,大大减少编程复杂度
而,我们每次需要的线段信息,只需要在线段树中检索, 线段树的实质是,一颗存储着线段信息的二叉搜索树。
我们把一个长的线段(区间),不断的二分划分,即[a,b]划分成[a,(a+b)>>1](leftchild),[(a+b)>>1+1,b](rightchild); 直到a=b为止。
我们把a=b的这个线段称为叶子节点。在某些题内,叶子节点存储的就是该点在线性结构所对应的信息。
由上可知,我们不断划分着[a,b]那么对于每一个[a,(a+b)>>1],[(a+b)>>1+1,b]它的左儿子和右儿子也是满足"线段树"性质

上图是我们划分[1,10]区间的过程。现在我们回到问题1
我们发现如果是一个一个叶子结点访问,那么每次访问的时间甚至比O(n)的数组模拟要长。
那么我们再看一个简单版的问题1
问题2:
给定一个序列
1.修改其中一个点
Sub:单点减小某值
Add:单点增加某值
2.询问区间和
Query:询问区间和
现在我们可以通过每次查找到叶子节点,暴力修改。
然后再递归求解区间和。
具体代码如下:
const maxn=; var tree:packed array [..maxn] of longint; procedure pushup(now:longint);
begin
tree[now]:=tree[now<<]+tree[now<<+]; //区间求和
end; procedure build(l,r,now:longint);
var mid:longint;
begin
if l=r then
begin
read(tree[now]); //到叶子节点时输入信息并退出
exit;
end;
mid:=(l+r) shr ;
build(l,mid,now<<);
build(mid+,r,now<<+);
pushup(now);
end; procedure update(p,add,l,r,now:longint); //更新数据
var mid:longint;
begin
if l=r then
begin
inc(tree[now],add);
exit;
end;
mid:=(l+r) shr ;
if p<=mid then update(p,add,l,mid,now<<) //递归求解
else update(p,add,mid+,r,now<<+);
pushup(now);
end; function query(left,right,l,r,now:longint):longint; //询问和
var mid,ans:longint;
begin
if (left<=l) and (r<=right) then exit(tree[now]);
mid:=(l+r) shr ;
ans:=;
if left<=mid then inc(ans,query(left,right,l,mid,now<<));
if right>mid then inc(ans,query(left,right,mid+,r,now<<+));
exit(ans);
end; procedure ques(n:longint);
var ch:char; a,b:longint; temp:string;
begin
while true do
begin
temp:='';
ch:='';
while ch<>' ' do
begin
read(ch);
temp:=temp+ch;
end;
delete(temp,length(temp),);
if temp='End' then exit;
readln(a,b);
case temp of
'Add':update(a,b,,n,);
'Query':writeln(query(a,b,,n,));
'Sub':update(a,-b,,n,);
end;
end;
end; procedure main;
var i,t,n:longint;
begin
readln(t);
for i:= to t do
begin
readln(n);
build(,n,);
readln;
ques(n);
end;
end; begin
main;
end.
但是,我们如果对于区间有修改的话,这样就太慢了,O(Qnlogn),甚至比朴素算法都慢
那么,线段树的精华出现了,lazy-tag技术
Lazy-Tag 顾名思义,懒惰标记。我们每当要对一个区间进行修改时,
我们只需要访问到这个区间所包含的最大区间,然后对这个区间记一个lazy-tag
我们如此做的动机是:没有必要更新到叶子节点,而是在一个合适的范围内,上一个tag
而我们在询问的时候,每询问一个区间,查询其tag,若存在tag,则把tag下传到该区间的左儿子与右儿子,并把该区间的tag信息上传,且清空当前Tag。
这样我们能大大的增加效率。
pushdown应该如何写呢?
大概的结构是这样
if lazy[now]<>0 then
begin
do something;
end;
我们给出区间修改的问题3
维护一个数列,支持如下操作:
1.把区间[l,r]全部增加v
2.修改k点值为r
3.询问区间[l,r]的和
代码如下:(未写main函数)
//区间增减 区间求和
const maxn=; var lazy,tree:array [..maxn] of longint; procedure pushup(now:longint);
var left,right:longint;
begin
left:=now<<; right:=left+;
tree[now]:=tree[left]+tree[right];
end; procedure pushdown(now,l,r:longint);
var len,left,right,delta:longint;
begin
left:=now<<; right:=left+; delta:=lazy[now]; len:=r-l+;
if lazy[now]<> then
begin
inc(tree[now],delta*len);
inc(lazy[left],delta);
inc(lazy[right],delta);
lazy[now]:=;
end;
end; procedure build(l,r,now:longint);
var mid,left,right:longint;
begin
mid:=(l+r)>>; left:=now<<; right:=left+;
lazy[now]:=;
if l=r then
begin
read(tree[now]);
exit;
end;
build(l,mid,left);
build(mid+,r,right);
pushup(now);
end; procedure update(now,l,r:longint);
var mid,left,right:longint;
begin
mid:=(l+r)>>; left:=now<<; right:=left+;
pushdown(left,l,mid);
pushdown(right,mid+,r);
pushup(now);
end; procedure insert(left,right,c,l,r,now:longint);
var mid,leftch,rightch:longint;
begin
mid:=(l+r)>>; leftch:=now<<; rightch:=leftch+;
pushdown(now,l,r);
if (left<=l) and (r<=right) then
begin
inc(lazy[now],c);
exit;
end;
if left<=mid then insert(left,right,c,l,mid,leftch);
if mid<right then insert(left,right,c,mid+,r,rightch);
update(now,l,r);
end; function querysum(left,right,l,r,now:longint):longint;
var leftch,rightch,mid,ans:longint;
begin
pushdown(now,l,r);
if (left<=l) and (r<=right) then exit(tree[now]);
mid:=(l+r) shr ;
leftch:=now<<;
rightch:=leftch+;
ans:=;
if left<=mid then inc(ans,querysum(left,right,l,mid,leftch));
if mid<right then inc(ans,querysum(left,right,mid+,r,rightch));
exit(ans);
end;
我们再升级一下问题
维护一个数列,支持如下操作:
0.询问区间[l,r]的和
1.修改单点l值为r
2.把区间[l,r]全部置为v
3.把区间[l,r]全部增加v
看上去很麻烦,我们不知道区间覆盖和区间增减的顺序的话,可能标记会出错。
但是我们只需要再维护一个sign域即可,而我们也不必在意区间覆盖和区间增减的顺序
因为:当区间覆盖与区间增量tag同时存在时,我们优先区间覆盖标记,在清空区间覆盖标记时,把区间增量标记清空
为什么呢?很显然,我们先进行区间增减,再进行区间覆盖,那么显然区间增减无意义。
但是问题来了,若我们先进行的区间覆盖,再进行区间增减,那么区间增减也会被覆盖掉,这样显然会WA.
怎么解决呢?我们在下tag时,都查询一次当前结点的tag,若当前结点存在tag,那么我们清空tag,使该层tag传递到下一层,
即当前更新不影响历史记录。
这样,我们能保持标记总是sign在上,不影响delta的传递,即覆盖标记永远优先于增量标记,且传递深度总是较增量标记传递深度大一层。
代码如下:(不支持全部覆盖为0,若要全部覆盖为0则需重新初始化sign的值)
{$inline on}
const maxn=;
var tree,sign,delta:array [..maxn] of int64;
procedure pushup(now:longint); inline;
var left,right:longint;
begin
left:=now<<; right:=left+;
tree[now]:=tree[left]+tree[right];
end;
procedure pushdown(now,l,r:longint); inline;
var left,right,len:longint;
begin
left:=now<<; right:=left+;
len:=r-l+;
if sign[now]<> then
begin
sign[left]:=sign[now];
sign[right]:=sign[now];
delta[left]:=;
delta[right]:=;
tree[now]:=len*sign[now];
sign[now]:=;
end;
if delta[now]<> then
begin
inc(delta[left],delta[now]);
inc(delta[right],delta[now]);
inc(tree[now],delta[now]*len);
delta[now]:=;
end;
end;
procedure build(now,l,r:longint); inline;
var mid,left,right:longint;
begin
mid:=(l+r)>>; left:=now<<; right:=left+;
delta[now]:=; sign[now]:=;
if l=r then
begin
read(tree[now]);
exit;
end;
build(left,l,mid);
build(right,mid+,r);
pushup(now);
end;
procedure update(now,l,r:longint); inline;
var mid,left,right:longint;
begin
mid:=(l+r)>>; left:=now<<; right:=left+;
pushdown(left,l,mid);
pushdown(right,mid+,r);
pushup(now);
end;
procedure changepoint(pos,v,l,r,now:longint); inline;
var mid,left,right:longint;
begin
mid:=(l+r)>>; left:=now<<; right:=left+;
pushdown(now,l,r);
if l=r then
begin
tree[now]:=v;
exit;
end;
if pos<=mid then changepoint(pos,v,l,mid,left)
else changepoint(pos,v,mid+,r,right);
update(now,l,r);
end;
procedure change(left,right,v,l,r,now:longint); inline;
var mid,leftch,rightch:longint;
begin
pushdown(now,l,r);
mid:=(l+r)>>; leftch:=now<<; rightch:=leftch+;
if (left<=l) and (r<=right) then
begin
sign[now]:=v;
delta[now]:=;
exit;
end;
if left<=mid then change(left,right,v,l,mid,leftch);
if mid<right then change(left,right,v,mid+,r,rightch);
update(now,l,r);
end;
procedure insert(left,right,v,l,r,now:longint); inline;
var mid,leftch,rightch:longint;
begin
mid:=(l+r)>>; leftch:=now<<; rightch:=leftch+;
pushdown(now,l,r);
if (left<=l) and (r<=right) then
begin
inc(delta[now],v);
exit;
end;
if left<=mid then insert(left,right,v,l,mid,leftch);
if mid<right then insert(left,right,v,mid+,r,rightch);
update(now,l,r);
end;
function querysum(left,right,l,r,now:longint):int64; inline;
var leftch,rightch,mid:longint; ans:int64;
begin
pushdown(now,l,r);
if (left<=l) and (r<=right) then exit(tree[now]);
mid:=(l+r)>>;
leftch:=now<<;
rightch:=leftch+;
ans:=;
if left<=mid then inc(ans,querysum(left,right,l,mid,leftch));
if mid<right then inc(ans,querysum(left,right,mid+,r,rightch));
update(now,l,r);
exit(ans);
end;
procedure swap(var l,r:longint); inline;
begin
l:=l xor r;
r:=l xor r;
l:=l xor r;
end;
procedure main;
var i,n,m,que,l,r,a:longint;
begin
read(n,m);
build(,,n);
for i:= to m do
begin
read(que,l,r);
if l>r then swap(l,r);
case que of
:writeln(querysum(l,r,,n,));
:changepoint(l,r,,n,);
:
begin
read(a);
change(l,r,a,,n,);
end;
:
begin
read(a);
insert(l,r,a,,n,);
end;
end;
end;
end;
begin
main;
end.
另外:
线段树一定要用数组,指针常数巨大。
下面是指针与数组版本的耗时比较

此为指针版,耗时耗空间都相当大(不排除本人蒟蒻原因写丑了)

此为数组版,常数还是比较小的。
Pascal 线段树 lazy-tag 模板的更多相关文章
- _bzoj1798 [Ahoi2009]Seq 维护序列seq【线段树 lazy tag】
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1798 注意,应保证当前节点维护的值是正确的,lazy tag只是一个下传标记,在下传时应即时 ...
- JuQueen(线段树 lazy)
JuQueen Time Limit: 5 Sec Memory Limit: 512 MB Description Input Output Sample Input 10 10 5 state ...
- 分块+lazy 或者 线段树+lazy Codeforces Round #254 (Div. 2) E
E. DZY Loves Colors time limit per test 2 seconds memory limit per test 256 megabytes input standard ...
- hdu1698(线段树区间替换模板)
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1698 题意: 第一行输入 t 表 t 组测试数据, 对于每组测试数据, 第一行输入一个 n , 表示 ...
- POJ 2777——线段树Lazy的重要性
POJ 2777 Count Color --线段树Lazy的重要性 原题 链接:http://poj.org/problem?id=2777 Count Color Time Limit: 1000 ...
- poj 3237 树链剖分模板(用到线段树lazy操作)
/* 本体在spoj375的基础上加了一些操作,用到线段树的lazy操作模板类型 */ #include<stdio.h> #include<string.h> #includ ...
- 线段树lazy模板 luogu3372
线段树写得很少,这么基本的算法还是要会的…… #include<bits/stdc++.h> using namespace std; inline long long read() { ...
- A Simple Problem with Integers(线段树区间更新模板)
最基本的线段树的区间更新及查询和 用tag(lazy)数组来“延缓”更新,查询或添加操作必须进行pushdown操作,即把tag从p传到lp和rp并清楚tag[p],既然得往lp和rp递归,那么就可以 ...
- POJ 3468:A Simple Problem with Integers(线段树区间更新模板)
A Simple Problem with Integers Time Limit: 5000MS Memory Limit: 131072K Total Submissions: 141093 ...
随机推荐
- Learn X in Y minutes(python一页纸代码)
一篇非常好的文章,解释了python基本语法的方方面面: # Single line comments start with a hash. """ Multiline ...
- Jquery html页面处理基础
1.怎样获得浏览器的可视高度? var windHight = $(window).height(); //获得浏览器的可视高度 2.怎样获得滚动条相对于顶部的高度? var scrollHi ...
- java快速排序1000万无序数组JVM-Xmx=256M 耗时2s
自己动手写排序算法,快速排序是比较不好写的了~ import java.util.*; class Test{ public void quickSort(int[] arr,int low,int ...
- 在magento中定义static block
在magento中如何调用static block?(系统面板内CMS---->static block) 解答:若想在站点页面的某个地方放点静态的内容,比如广告,或者是促销信息之类的,这样的东 ...
- python导入模块的方法
先看代码: import time #利用import print "how", time.sleep(2) #sleep()方法前面必须得有导入模块的名字time print & ...
- BNUOJ27873:A Special "Happy Birthday" Song!!!
There are n people (excluding myself) in my 30th birthday party. They sing the traditional "hap ...
- i = i++ 在java字节码层面的分析
有这么一段代码: package zl.test; public class PcodeTest { /** * @param args */ public static void main(Stri ...
- Android源码学习(一) 数据集观察者
查看Android源码发现这个,决定记下下来. 1.在android.database这个包下面,存在这样一个抽象类DataSetObserver,里面包括onChanged()和onInvalida ...
- android入门——BroadCast(1)
使用广播要定义一个广播接收类,如 package com.example.wkp.broadcast; import android.content.BroadcastReceiver; import ...
- JS 去除特定符号(逗号)的方法
<script language="javascript"> var str="asdfk,asdf345345,345345"; //替换除数字与 ...