先说下我的代码风格(很丑,勿喷)

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 模板的更多相关文章

  1. _bzoj1798 [Ahoi2009]Seq 维护序列seq【线段树 lazy tag】

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1798 注意,应保证当前节点维护的值是正确的,lazy tag只是一个下传标记,在下传时应即时 ...

  2. JuQueen(线段树 lazy)

    JuQueen Time Limit: 5 Sec  Memory Limit: 512 MB Description Input Output Sample Input 10 10 5 state ...

  3. 分块+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 ...

  4. hdu1698(线段树区间替换模板)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1698 题意: 第一行输入 t 表 t 组测试数据, 对于每组测试数据, 第一行输入一个 n , 表示 ...

  5. POJ 2777——线段树Lazy的重要性

    POJ 2777 Count Color --线段树Lazy的重要性 原题 链接:http://poj.org/problem?id=2777 Count Color Time Limit: 1000 ...

  6. poj 3237 树链剖分模板(用到线段树lazy操作)

    /* 本体在spoj375的基础上加了一些操作,用到线段树的lazy操作模板类型 */ #include<stdio.h> #include<string.h> #includ ...

  7. 线段树lazy模板 luogu3372

    线段树写得很少,这么基本的算法还是要会的…… #include<bits/stdc++.h> using namespace std; inline long long read() { ...

  8. A Simple Problem with Integers(线段树区间更新模板)

    最基本的线段树的区间更新及查询和 用tag(lazy)数组来“延缓”更新,查询或添加操作必须进行pushdown操作,即把tag从p传到lp和rp并清楚tag[p],既然得往lp和rp递归,那么就可以 ...

  9. POJ 3468:A Simple Problem with Integers(线段树区间更新模板)

    A Simple Problem with Integers Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 141093 ...

随机推荐

  1. 一次非典型的SQL报错

    昨天调试一个表值函数,结果出现了这个错误. mplicit conversion of varchar value to varchar cannot be performed because the ...

  2. 下载PHPDroid: 基于WebView和PHP内置HTTP服务器开发Android应用

    基于Android上的PHP(比如我打包的PHPDroid),寥寥几行PHP代码,就能实现一个支持无线局域网用浏览器访问的Android手机的Shell,用于执行命令和PHP代码.       个人在 ...

  3. c/c++与java------之JNI学习(一)

    一.java 调用c/c++ 步骤: 1.在java类中创建一个native关键字声明的函数 2.使用javah生成对应的.h文件 3.在c/c++中实现对应的方法 4.使用vs2012创建一个win ...

  4. winform制作自定义控件(入门)

    原文链接:http://blog.csdn.net/bychentufeiyang/article/details/7081402   与原文基本一致,只是例子变成VS2012环境,语言采用博主常用的 ...

  5. Oracle EBS-SQL (WIP-14):检查车间需求与BOM差异对照.sql

    select a.*, b.BOM定额, a.WIP定额 - b.BOM定额     差异 from (select WE.WIP_ENTITY_NAME                        ...

  6. PVRTC 纹理

    iPhone的图形芯片(PowerVR MBX)对一种称为 PVRTC 的压缩技术提供的硬件支持,Apple推荐在开发iPhone应用程序时使用 PVRTC 纹理.他们甚至提供了一篇很好的技术笔记描述 ...

  7. 50% 的财富 500 强企业使用 Windows Azure

    在上周的北美TechEd大会上,我有幸见到了来自世界各地的客户.合作伙伴和分析师,其数量之多,让人震惊.没有什么比亲耳聆听使用 Windows Azure 来开创新天地的客户亲口讲述他们的故事更令人振 ...

  8. linux环境开发私房菜

    1,各种linux 平台GUI开发IDE环境 2,C/C++ 好的编译器 gcc/emcs;

  9. MySQL Troubleshoting:Waiting on query cache mutex

    今天被MySQL Query Cache 炕了.线上大量 Waiting on query cache mutex 那么什么是 Query Cache? QC 缓存的是整个SELECT的结果集.而非执 ...

  10. How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API?

    ou can customize the JsonSerializerSettings by using theFormatters.JsonFormatter.SerializerSettings  ...