poj3468 线段树的懒惰标记

题目链接:poj3468

题意:给定一段数组,有两种操作,一种是给某段区间加c,另一种是查询一段区间的和

思路:暴力的方法是每次都给这段区间的点加c,查询也遍历一遍区间,复杂度是n*n,肯定过不去,另一种思路是用线段树记录区间的和,每次查询的复杂度是lgn,修改不必更新到每个点,当某个区间全被修改时,我们可以给它加一个懒惰标记,表示这个区间的所有下面节点都需要更新,只是因为现在不需要使用而暂时没有更新。这样修改的复杂度也降到了lgn

ac代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e5+10;
long long num[maxn],sum[maxn*4],lazy[maxn*4];
void pushdown(int rt,int len1,int len2)//向下更新懒惰标记
{
    if(lazy[rt])
    {
        lazy[rt*2]+=lazy[rt];//注意是+=而不是=
        lazy[rt*2+1]+=lazy[rt];
        sum[rt*2]+=lazy[rt]*len1;
        sum[rt*2+1]+=lazy[rt]*len2;
        sum[rt]=sum[rt*2]+sum[rt*2+1];
        lazy[rt]=0;
    }
}
void build(int st,int en,int rt)
{
    if(st==en)
    {
        sum[rt]=num[st];
        return;
    }
    build(st,(st+en)/2,rt*2);
    build((st+en)/2+1,en,rt*2+1);
    sum[rt]=sum[rt*2]+sum[rt*2+1];
}
void add(int l,int r,int c,int st,int en,int rt)
{
     int md=(st+en)/2;
     if(l<=st&&r>=en)
     {
         lazy[rt]+=c;
         sum[rt]+=(en-st+1)*c;
         return ;
     }
     pushdown(rt,md-st+1,en-md);
     if(r>=md+1)
     {
         add(l,r,c,md+1,en,rt*2+1);
     }
     if(l<=md)
     {
         add(l,r,c,st,md,rt*2);
     }
     sum[rt]=sum[rt*2]+sum[rt*2+1];
}
long long quer(int l,int r,int rt,int st,int en)
{
    long long res=0,md=(st+en)/2;
    if(l<=st&&r>=en)
        return sum[rt];
    pushdown(rt,md-st+1,en-md);
    if(r>=md+1)
        res+=quer(l,r,rt*2+1,md+1,en);
    if(l<=md)
        res+=quer(l,r,rt*2,st,md);
    return res;
}
int main()
{
    char comd;
    int n,q;
    cin>>n>>q;
    for(int i=1;i<=n;i++)
        scanf("%lld",&num[i]);
    build(1,n,1);
    for(int i=1;i<=q;i++)
    {
        int l,r,c;
        cin>>comd;
        if(comd=='C')
        {
            scanf("%d %d %d",&l,&r,&c);
            add(l,r,c,1,n,1);
        }
        else
        {
            scanf("%d %d",&l,&r);
            printf("%lld\n",quer(l,r,1,1,n));
        }
    }
    return 0;
}

  

提升题:hdu6315

题意:有ab两个数组,有两种操作,一种是给a数组的一段区间加一,另一种是求a/b数组的累加和

思路:只有当一段区间最大的a大于最小的b时,这段区间的答案才会发生改变,如果没有发生改变,那么我们就不必要去给子区间修改最大a的值。我们给这段区间加个懒惰标记就可以了,以后浏览到这个区间时我们再修改。复杂度为nlgn

ac代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e5+10;
int sum[maxn*4],maxa[maxn*4],minb[maxn*4],b[maxn],lazy[maxn*4];

void pushup(int rt)
{
    sum[rt]=sum[rt*2+1]+sum[rt*2];
    maxa[rt]=max(maxa[rt*2],maxa[rt*2+1]);
    minb[rt]=min(minb[rt*2],minb[rt*2+1]);
}
void pushdowm(int rt)
{
    if(lazy[rt])
    {
        lazy[rt*2]+=lazy[rt];
        lazy[rt*2+1]+=lazy[rt];
        maxa[rt*2]+=lazy[rt];
        maxa[rt*2+1]+=lazy[rt];
        lazy[rt]=0;
    }
}
void build(int st,int en,int rt)
{
    if(st==en)
    {
        minb[rt]=b[st];
        return;
    }
    build(st,(st+en)/2,rt*2);
    build((st+en)/2+1,en,rt*2+1);
    pushup(rt);
}
void add(int l,int r,int st,int en,int rt)
{
    int md=(st+en)/2;
    pushdowm(rt);
    if(l<=st&&r>=en)
    {
        maxa[rt]++;
        if(maxa[rt]>=minb[rt])
        {
            if(st!=en)
            {
                add(l,r,md+1,en,rt*2+1);
                add(l,r,st,md,rt*2);
                pushup(rt);
            }
            else
            {
                while(maxa[rt]>=minb[rt])
                {
                    minb[rt]+=b[st];
                    sum[rt]++;
                }
            }
        }
        else
            lazy[rt]++;
        return;
    }
    else
    {
        if(l<=md)
            add(l,r,st,md,rt*2);
        if(r>=md+1)
            add(l,r,md+1,en,rt*2+1);
    }
    pushup(rt);
}
int quer(int l,int r,int st,int en,int rt)
{
    pushdowm(rt);
    int md=(st+en)/2;
    if(l<=st&&r>=en)
        return sum[rt];
    int res=0;
    if(l<=md)
        res+=quer(l,r,st,md,rt*2);
    if(r>=md+1)
        res+=quer(l,r,md+1,en,rt*2+1);
    pushup(rt);
    return res;
}
int main()
{
    int n,q;
    char comd[10];
    while(cin>>n>>q)
    {
        for(int i=0;i<maxn*4;i++)sum[i]=0,maxa[i]=0,minb[i]=0,lazy[i]=0;
        for(int i=0;i<maxn;i++)lazy[i]=0;
        
        for(int i=1; i<=n; i++)
            scanf("%d",&b[i]);
        build(1,n,1);
        for(int i=1; i<=q; i++)
        {
            int l,r;
            scanf("%s %d %d",&comd,&l,&r);
            if(comd[0]=='a')
                add(l,r,1,n,1);
            else
                printf("%d\n",quer(l,r,1,n,1));
        }
    }
    return 0;
}

  

 总结:懒惰标记可以解决一些区域修改问题

posted @ 2018-08-20 10:19  czh~  阅读(435)  评论(0编辑  收藏  举报