线段树与lazy标记

线段树区间修改与区间查询,如果我们对区间中的每一个值依次进行修改,那么最终结果一定是tle。因此我们在这里加入lazy标记。

lazy标记,对于区间更新而言,如果我首次更新这段区域,我就先将需要更新的值加到lazy标签中,暂时不向子节点更新。在第二次更新过程中,我们查到此点,则顺手将此点的lazy标签中的值,更新到子节点,然后再重置lazy标签。这样一来,就将区间更新的时间复杂度,进一步降低。

贴一下模板:

long long tr[M*4],a[M],add[M*4];
void pushdown(int i,int len)
{
    if(add[i])
    {
        add[i<<1]+=add[i];
        add[i<<1|1]+=add[i];
        tr[i<<1]+=add[i]*(len-len/2);
        tr[i<<1|1]+=add[i]*(len/2);
        add[i]=0;
    }

}
void pushup(int i)
{
    tr[i]=tr[i*2]+tr[i*2+1];
}
void build(int i,int l,int r)
{
    if(l==r)
    {
        tr[i]=a[l];
        return;
    }
    int mid =(l+r)>>1;
    build(2*i,l,mid);
    build(2*i+1,mid+1,r);
    pushup(i);
}
void range_update(int i,int l,int r,int x,int y,int t)
{
    if(x<=l&&y>=r)
    {
        add[i]+=t;
        tr[i]=tr[i]+(r-l+1)*t;
        return;
    }
    pushdown(i,r-l+1);
    int mid=(l+r)>>1;
    if(x<=mid) range_update(2*i,l,mid,x,y,t);
    if(y>mid) range_update(2*i+1,mid+1,r,x,y,t);
    pushup(i);
}
long long query(int i,int l,int r,int x,int y)
{
    long long sum=0;
    if(x<=l&&y>=r)
    {
        return tr[i];
    }
    pushdown(i,r-l+1);
    int mid=(r+l)>>1;
    if(x<=mid)sum+=query(2*i,l,mid,x,y);
    if(y>mid) sum+=query(2*i+1,mid+1,r,x,y);
    pushup(i);
    return sum;
}

线段树例题:

nefu1475:

这道题是对于区间进行sqrt()操作,sqrt()操作是没办法用区间更新的,lazy没办法对一个区间sqrt进行操作。

对于一个值进行sqrt操作很快就可以将原本的值变为1,如果变为1了,显然不需要再次进行sqrt操作了,那么,我们对于整个区间进行标记,如果这个区间都不需要更新,那么,我们直接跳出,那不就加快了程序的运行吗。

那么我们建立一个新的数组flag数组,对于这个数组跟tr数组结构相同,同时,其中储存的是我们想得到的区间是否需要更新,如果,需要区间内所有的值不是0,就是1,那么就说明这个区间不需要更新,那么我们就可以直接跳出。

#include <cstdio>
#include <cstring>
#include <cctype>
#include <string>
#include <set>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
#define ll long long
using namespace std;
const int M=2e5+5;
ll tr[M*4];
int flag[M*4],n,m;
void pushup(int i)
{
    tr[i]=tr[2*i]+tr[2*i+1];
    flag[i]=flag[i*2+1]&flag[i*2];//对flag区间进行&运算,对于非0即1的flag数组,如果全是1则为1,其他情况都为0,都需要更新
}
void build(int i,int l,int r)
{
    if(l==r)
    {
        scanf("%lld",&tr[i]);
        if(tr[i]<=1) flag[i]=1;//如果是1,或者0那么我们直接将flag变成1,这一段我们就不更新了
        return;
    }
    int mid=(r+l)>>1;
    build(i*2,l,mid);
    build(i*2+1,mid+1,r);
    pushup(i);
}
void update(int i,int l,int r,int x,int y)//更新时直接对这个区间进行查询更新,如果这个区间所有值都为1或0那么我们就不更新直接跳出。
{
    if(flag[i]==1)return;
    if(l==r)
    {
        tr[i]=(ll)sqrt(tr[i]);
        if(tr[i]<=1) flag[i]=1;
        return;
    }
    int mid=(r+l)>>1;
    if(x<=mid) update(i*2,l,mid,x,y);
    if(y>=mid+1) update(i*2+1,mid+1,r,x,y);
    pushup(i);
}
ll query(int i,int l,int r,int x,int y)
{
    ll ans=0;
    if(x<=l&&y>=r)
    {
        return tr[i];
    }
    int mid=(r+l)/2;
    if(x<=mid)ans+=query(i*2,l,mid,x,y);
    if(y>mid) ans+=query(i*2+1,mid+1,r,x,y);
    return ans;
}
int main()
{
   while(~scanf("%d",&n))
   {
            build(1,1,n);
        scanf("%d",&m);
        while(m--)
        {
            int x,y,o;
            scanf("%d%d%d",&o,&x,&y);
            if(o==1)
            {
                printf("%lld\n",query(1,1,n,x,y));
            }
            else
            {
                update(1,1,n,x,y);
            }
        }
   }
    return 0;
}

我认为,flag数组跟lazy数组有一定的相似性。都与tr有着相同的结构,对于区间修改降低时间复杂度服务。

猜你喜欢

转载自blog.csdn.net/Black__wing/article/details/81451101