【例题】【线段树】lazy

1、
(1)、Lazy思想:对整个结点进行的操作,先在结点上做标记,而并非真正执行,直到根据查询操作的需要分到下层。
(2)、延迟标记 Lazy:如果需要对一个区间中每一个叶结点进行操作,我们不妨先别忙着操作,而是在所有大区间上做一个标记, 下一次遇到或要用到时,再进行处理(标记传递),达到减少操作次数,提高线段树的效率的作用。

2、
NKOJ 2297 数列操作2
时间限制 : 10000 MS 空间限制 : 165536 KB
问题描述
给出一列数{Ai}(1≤i≤n),总共有m次操作,操作分如下两种:
1.ADD x y z 将x到y区间的所有数字加上z
2.ASK x y 将x到y区间的最大一个数字输出

输入格式
第一行,一个整数n
第二行,n个空格间隔的整数,表示数列一开始的情形
第三行,一个整数m
接下来m行,表示m条操作

样例输入
5
1 2 3 2 5
5
ADD 1 4 3
ASK 2 3
ASK 3 5
ADD 2 4 2
ASK 2 5

样例输出
6
6
8

提示
m,n<=100000
x,y<=100000
z<=1000

思路
lazy

代码:

#include<iostream>
#include<cstdio>
using namespace std;
const int need=200003;//数据有鬼 

struct fy{int a,b,val,lazy;}t[need*4];

int a[need];
int x,y,z;

void build(int s,int x,int y)
{
    t[s].a=x,t[s].b=y;
    if(x==y)
    {
        t[s].val=a[x];
        return;
    }
    build(s<<1,x,(x+y)/2),build((s<<1)|1,(x+y)/2+1,y);
    t[s].val=max(t[s<<1].val,t[(s<<1)|1].val);
}

void putdown(int s)
{
    if(t[s].a==t[s].b) return;
    int k=t[s].lazy;
    t[s].lazy=0;
    t[s<<1].val+=k;
    t[s<<1].lazy+=k;
    t[(s<<1)|1].val+=k;
    t[(s<<1)|1].lazy+=k;
}

void add(int s)
{
    if(t[s].lazy!=0) putdown(s);
    if(x<=t[s].a&&t[s].b<=y)
    {
        t[s].lazy+=z;
        t[s].val+=z;
        return ;
    }
    if(!(y<t[s<<1].a||x>t[s<<1].b)) add(s<<1);
    if(!(y<t[(s<<1)|1].a||x>t[(s<<1)|1].b)) add((s<<1)|1);
    t[s].val=max(t[s<<1].val,t[(s<<1)|1].val);
}

int getmax(int s)
{
    if(t[s].lazy!=0) putdown(s);
    if(x<=t[s].a&&t[s].b<=y) return t[s].val;
    int ans=-need;
    if(!(y<t[s<<1].a||x>t[s<<1].b)) ans=max(ans,getmax(s<<1));
    if(!(y<t[(s<<1)|1].a||x>t[(s<<1)|1].b)) ans=max(ans,getmax((s<<1)|1));
    return ans;
}

int main()
{
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build(1,1,n);
    int m;scanf("%d",&m);
    string a;
    for(int i=1;i<=m;i++)
    {
        cin>>a;
        if(a=="ADD")
        {
            scanf("%d%d%d",&x,&y,&z);
            add(1);
        }
        else
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",getmax(1));
        }
    }
}

2、
NKOJ 2295 涂色
时间限制 : 10000 MS 空间限制 : 165536 KB

问题描述
在数轴上进行一系列操作。每次操作有两种类型,一种是在线段[a,b]上涂上颜色,另一种将[a,b]上的颜色擦去。
问经过一系列的操作后,有多少条单位线段[k,k+1]被涂上了颜色。

输入格式
第一行,一个整数n,表示总的操作数
以后n行输入三个整数(n<=100000)。
第一个数为1表示涂色,0表示擦去。
第二、三个数表示线段a,b

输出格式
有多少条单位线段[k,k+1]被涂上了颜色。

输入样例1:
5
1 1 15
0 4 9
1 7 18
1 7 9
0 1 3
输入样例2:
4
1 1 8
0 4 8
1 7 8
0 1 3

输出样例1:
12
输出样例2:
2

思路
1、因为题所求为线段数量,而以点为叶节点不好讨论,所以叶节点表示一个单位线段,即[x,mid]、[mid,y]分别为左儿子和右儿子表示的区间。
注意a+1==b时才表示为叶节点。
2、lazy不仅存储要下放的状态还表示该区间的状态。
因为状态较少,且子节点和父节点状态很可能一致,所以才这样处理。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
const int need=200013;

struct fy{int a,b,v,lazy;}t[need*2]; //-1 clean   1 paint 0 表示该区间中既有涂了颜色的也有没涂的 

int c[need>>1],le[need*2],ri[need*2];
int tot=0,x,y;

void build(int x,int y)
{
    int s=++tot;
    t[s].a=x,t[s].b=y;
    if(x+1==y) 
    {
        t[s].lazy=-1;//一开始应全未涂色
        return ;
    }
    le[s]=tot+1;build(x,(x+y)>>1);
    ri[s]=tot+1;build((x+y)>>1,y);
}

void putdown(int s)
{
    if(t[s].a+1==t[s].b) return;//不判断就下放,会越界 
    t[le[s]].lazy=t[ri[s]].lazy=t[s].lazy;//无论之前什么状态都可以直接覆盖
    t[s].lazy=0;
}
void clean(int s)
{
    if(t[s].lazy<0) return ;
    if(x<=t[s].a&&t[s].b<=y)
    {
        t[s].lazy=-1;
        return ;
    }//如果“putdown”中没判断是否为叶节点,这里就要先判断。 
    if(t[s].lazy>0) putdown(s);
    if(!(t[le[s]].a>y||t[le[s]].b<x)) clean(le[s]);
    if(!(t[ri[s]].a>y||t[ri[s]].b<x)) clean(ri[s]);
} 
void paint(int s)
{
    if(t[s].lazy>0) return ;
    if(x<=t[s].a&&t[s].b<=y)
    {
        t[s].lazy=1;
        return ;
    }
    if(t[s].lazy<0) putdown(s);
    if(!(t[le[s]].a>y||t[le[s]].b<x)) paint(le[s]);
    if(!(t[ri[s]].a>y||t[ri[s]].b<x)) paint(ri[s]);
}

int solve(int s)
{
    if(t[s].lazy>0) return t[s].b-t[s].a;//因为树中叶节点为线段,所以线段数量为点数减一,即该处不用加一
    if(t[s].lazy<0) return 0;
    return solve(le[s])+solve(ri[s]);//因为叶节点不会出现lazy=0的情况,所以不用判断s是否为叶节点 
}

int main()
{
    int n;scanf("%d",&n);
    build(1,need-13);
    for(int a,i=1;i<=n;i++)
    {
        scanf("%d%d%d",&a,&x,&y);
        if(a) paint(1);
        else clean(1);
    }
    printf("%d",solve(1));
}

相似题:
http://blog.csdn.net/Y__XV/article/details/51890624 (lazy 相似)
http://blog.csdn.net/Y__XV/article/details/51913412

3、
http://blog.csdn.net/Y__XV/article/details/51886605

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值