zoukankan      html  css  js  c++  java
  • 浅谈算法——线段树之Lazy标记

    一.前言

    前面我们已经知道线段树能够进行单点修改和区间查询操作(基本线段树)。那么如果需要修改的是一个区间该怎么办呢?如果是暴力修改到叶子节点,复杂度即为(O(nlog n)),显然是十分不优秀的。那么我们能不能向区间查询一样把复杂度降到(O(log n))呢?

    二.算法流程

    线段树肯定是兹瓷(O(log n))修改的,否则发明它有何用处?所以,我我们现在需要知道,如何快速进行区间修改操作。首先,我们回顾下终止节点

    假定我要在这个图上修改区间[2,8],我只要修改掉图上所有的终止节点即可。于是复杂度就成功降到(O(log n))
    那么,我在终止节点上加什么东西呢?这里,我们就要引进(Lazy)标记。修改区间的时候,在这个区间所有的终止节点上打上一个标记,代表我这个点要加多少值。
    区间查询的时候,如果访问到某个有标记的点,但又不能使用该点的值的时候,就把标记下传(有点绕,看看代码应该就明了了),其他时候直接取值就好

    三.例题

    1.Pku3468 A Simple Problem with Integers

    Description
    You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of op
    eration is to add some given number to each number in a given interval. The other is to ask for the
    sum of numbers in a given interval.
    1.给[a ,b]整体上加上一个常数c。
    2.查询[a ,b]区间的和。

    Input
    The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
    The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.
    Each of the next Q lines represents an operation.
    "C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.
    "Q a b" means querying the sum of Aa, Aa+1, ... , Ab.

    Output
    You need to answer all Q commands in order. One answer in a line. The sums may exceed the range of 32-bit integers

    Sample Input
    10 5
    1 2 3 4 5 6 7 8 9 10
    Q 4 4
    Q 1 10
    Q 2 4
    C 3 6 3
    Q 2 4

    Sample Output
    4
    55
    9
    15

    HINT
    对于100%的数据:N<=100000,M<=100000

    区间修改,区间查询,所以用到Lazy标记,代码里会有详细注释

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define inf 0x7f7f7f7f
    using namespace std;
    typedef long long ll;
    typedef unsigned int ui;
    typedef unsigned long long ull;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	for (;ch<'0'||ch>'9';ch=getchar())	if (ch=='-')    f=-1;
    	for (;ch>='0'&&ch<='9';ch=getchar())	x=(x<<1)+(x<<3)+ch-'0';
    	return x*f;
    }
    inline void print(int x){
    	if (x>=10)     print(x/10);
    	putchar(x%10+'0');
    }
    const int N=1e5;
    int n,m;
    ll val[N+10];
    struct Segment{
    	#define ls (p<<1)
    	#define rs (p<<1|1)
    	ll tree[N*4+10],cnt[N*4+10];
    	void updata(int p){tree[p]=(tree[ls]+tree[rs]);}	//updata的内容具体情况具体分析 
    	void add_cnt(int p,int v,int t){	//标记不影响当前节点的值,谨记这点 
    		tree[p]=tree[p]+1ll*v*t;		//t是区间长度,加标记需要乘上这个区间 
    		cnt[p]=cnt[p]+v;				//标记更新 
    	}
    	void pushdown(int p,int l,int r){	//加标记下传 
    		if (!cnt[p])	return;
    		int mid=(l+r)>>1;
    		add_cnt(ls,cnt[p],mid-l+1),add_cnt(rs,cnt[p],r-mid);	//加标记的更新 
    		cnt[p]=0;
    	}
    	void build(int p,int l,int r){
    		cnt[p]=0;
    		if (l==r){
    			tree[p]=val[l];		//val[i]基本是开始读入的数
    			return;
    		}
    		int mid=(l+r)>>1;
    		build(ls,l,mid),build(rs,mid+1,r);
    		updata(p);
    	}
    	void change(int p,int l,int r,int x,int y,int t){	//t是标记 
    		if (x<=l&&r<=y){
    			add_cnt(p,t,r-l+1);	//打加标记 
    			return;
    		}
    		int mid=(l+r)>>1;
    		pushdown(p,l,r);	//修改两边的时候记得传递标记 
    		if (x<=mid)	change(ls,l,mid,x,y,t);			//访问左儿子 
    		if (y>mid)	change(rs,mid+1,r,x,y,t);		//访问右儿子 
    		updata(p);		//记得更新 
    	}
    	ll query(int p,int l,int r,int x,int y){
    		if (x<=l&&r<=y)	return tree[p];		//在区间内直接输出 
    		int mid=(l+r)>>1;
    		ll ans=0;
    		pushdown(p,l,r);	//传递标记 
    		if (x<=mid)	ans=ans+query(ls,l,mid,x,y);	//访问左儿子 
    		if (y>mid)	ans=ans+query(rs,mid+1,r,x,y);	//访问右儿子 
    		return ans;
    	}
    }Tree;
    char s;
    int main(){
    	n=read(),m=read();
    	for (int i=1;i<=n;i++)	val[i]=read();
    	Tree.build(1,1,n);
    	for (int i=1;i<=m;i++){
    		cin>>s;
    		int x=read(),y=read(),z;
    		if (s=='Q')	printf("%lld
    ",Tree.query(1,1,n,x,y));
    		if (s=='C')	z=read(),Tree.change(1,1,n,x,y,z);
    	}
    	return 0;
    }
    

    2.

    Description
    给定一个正整数序列A,要求支持以下操作
    1): + a b c 表示在[a,b]上加上一个常数C。
    2): * a b c 在[a,b]上乘上一个常数K。
    3): QUERY a b 查询[a,b]的sum。

    Input
    第一行两个正整数n、m,n表示序列长度,m表示操作数
    第二行n个正整数,第i表示A[i]的大小
    接下来的m行,每行有且仅有一种操作,具体和题目描述一致
    n,m<=100000
    其他权值都<=50000

    Output
    对于每个询问操作,输出答案对1000000007取余的结果

    Sample Input
    10 10
    50 14 20 18 19 11 43 43 26 44

    • 3 6 41
      QUERY 1 2
    • 5 5 14
      QUERY 1 3
      QUERY 4 4
      QUERY 2 6
    • 3 5 31
    • 4 8 20
    • 5 8 28
      QUERY 6 7

    Sample Output
    64
    125
    59
    260
    53200

    同样是区间修改,区间查询,不过本题比上题要难一些。为什么呢,因为它需要维护两个标记。
    如果是两个互不相干的标记还好说,但这两个标记互相干扰!!!
    因此,我们在处理这类有多个标记的题目的时候,需要考虑下这些标记互相之间的影响,标记与标记之间的先后更新顺序。
    如本题,加标记与乘标记势必会互相影响
    如果我们先更新加标记,那么乘标记就会变成一个实型,非常不方便。于是我们让乘标记先更新,加标记就只需要乘上一个乘标记即可,大大降低了实现难度。
    (注释基本没有,不过也请读者凑合着看吧)

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define inf 0x7f7f7f7f
    using namespace std;
    typedef long long ll;
    typedef unsigned int ui;
    typedef unsigned long long ull;
    inline int read(){
        int x=0,f=1;char ch=getchar();
        for (;ch<'0'||ch>'9';ch=getchar())    if (ch=='-')    f=-1;
        for (;ch>='0'&&ch<='9';ch=getchar())  x=(x<<1)+(x<<3)+ch-'0';
        return x*f;
    }
    inline void print(int x){
        if (x>=10)     print(x/10);
        putchar(x%10+'0');
    }
    const int N=1e5,mod=1e9+7;
    int n,m;
    struct Segment{
        #define ls (p<<1)
        #define rs (p<<1|1)
        int tree[N*4+10],cnt[N*4+10],res[N*4+10],size[N*4+10];
        void updata(int p){tree[p]=(tree[ls]+tree[rs])%mod;}
        void add_cnt(int p,int v){
            tree[p]=(tree[p]+1ll*v*size[p])%mod;
            cnt[p]=(cnt[p]+v)%mod;
        }
        void pushdown_cnt(int p){//加标记下传
            if (!cnt[p])    return;
            add_cnt(ls,cnt[p]),add_cnt(rs,cnt[p]);
            cnt[p]=0;
        }
        void add_res(int p,int v){
            tree[p]=1ll*tree[p]*v%mod;
            res[p]=1ll*res[p]*v%mod;
            cnt[p]=1ll*cnt[p]*v%mod;
        }
        void pushdown_res(int p){//乘标记下传
            if (res[p]==1)  return;
            add_res(ls,res[p]),add_res(rs,res[p]);
            res[p]=1;
        }
        void pushdown(int p){pushdown_res(p),pushdown_cnt(p);}
        void build(int p,int l,int r){
            cnt[p]=0,res[p]=1,size[p]=r-l+1;
            if (l==r){
                tree[p]=read();
                return;
            }
            int mid=(l+r)>>1;
            build(ls,l,mid),build(rs,mid+1,r);
            updata(p);
        }
        void change(int p,int l,int r,int x,int y,int Cnt,int Res){
            if (x<=l&&r<=y){
                add_res(p,Res),add_cnt(p,Cnt);
                return;
            }
            int mid=(l+r)>>1;
            pushdown(p);
            if (x<=mid)  change(ls,l,mid,x,y,Cnt,Res);
            if (y>mid)   change(rs,mid+1,r,x,y,Cnt,Res); 
            updata(p);
        }
        int query(int p,int l,int r,int x,int y){
            if (x<=l&&r<=y)   return tree[p];
            int mid=(l+r)>>1,ans=0;
            pushdown(p);
            if (x<=mid)  ans=(ans+query(ls,l,mid,x,y))%mod;
            if (y>mid)   ans=(ans+query(rs,mid+1,r,x,y))%mod;
            return ans;
        }
    }Tree;
    char s[10];
    int main(){
        n=read(),m=read();
        Tree.build(1,1,n);
        for (int i=1;i<=m;i++){
            scanf("%s",s+1);
            int x=read(),y=read(),z;
            if (s[1]=='Q')  printf("%d
    ",Tree.query(1,1,n,x,y));
            if (s[1]=='+')  z=read(),Tree.change(1,1,n,x,y,z,1);
            if (s[1]=='*')  z=read(),Tree.change(1,1,n,x,y,0,z);
        }
        return 0;
    }
    

    3.最大连续子数列和

  • 相关阅读:
    可重入函数
    进程间通信的方法和实现
    Qt之Qprocess
    mysql学习(十二)内置函数
    mysql学习(十一)嵌套查询 排序 分组
    mysql学习(十)多表查询
    ubuntu 12.04 安装谷歌浏览器
    mysql学习(九)sql语句
    mysql学习(八)数据表类型-字符集
    mysql远程连接问题-http://www.cnblogs.com/jerome-rong/archive/2013/03/05/2944264.html
  • 原文地址:https://www.cnblogs.com/Wolfycz/p/8414546.html
Copyright © 2011-2022 走看看