zoukankan      html  css  js  c++  java
  • 线段树 区间修改

    我们对于线段树的区间修改你可以用一个最傻的办法循环进行单点修改(时间复杂度太高十分麻瓜)所以,我们要用一个聪明的做法延迟标记(LAZY)

    在限度拿书的“区间查询”指令中,每当遇到被询问区间[l,r]完全覆盖的节点时,可以立即把该节点上存储的信息作为候选答案返回。已经有大佬证明,被询问区间[l,r]在线段树上会被分成O(logN)个小区间(节点)从而在O(logN)的时间内求出答案。

    我们在执行修改指令时,同样可以在 L<= Pl <= Pr <= R 的情况下立即返回,只不过在回溯之前向节点P增加一个标记,标识“该节点曾经被修改过,但其子节点尚未被更新”。

    如果在后续的指令中,需要从节点P向下递归,我们再检查P是否具有标记。若有标记,就根据标记信息更新P的两个子节点,同时为P的两个子节点增加标记,然后清除P的标记。

    也就是说,除了在修改指令中直接划分成的O(logN)个节点之外,对任意给点的修改都延迟到“在后续操作中递归进入他的父亲节点时”再执行。这样一来,每条查询或修改指令的时间复杂度都降低到了O(logN)。这些标记被称为“延迟标记”。延迟标记提供了线段树中从上往下传递信息的方式。这种“延迟”也是设计算法与解决问题的一个重要思路。

    附上代码

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5+5;
    struct segment_tree{
    	int l,r;
    	long long sum,mark;
    }tree[maxn*4];
    long long a[maxn];
    int n,m;
    void pushup(int root){
    	tree[root].sum = tree[root<<1].sum+tree[root<<1|1].sum;
    }
    void pushdown(int root){
    	if(tree[root].mark){
    		tree[root<<1].sum += tree[root].mark*(tree[root<<1].r-tree[root<<1].l+1);
    		tree[root<<1|1].sum += tree[root].mark*(tree[root<<1|1].r-tree[root<<1|1].l+1);
    		tree[root<<1].mark += tree[root].mark;
    		tree[root<<1|1].mark += tree[root].mark;
    		tree[root].mark = 0;
    	}
    }
    void build(int L,int R,int root){
    	tree[root].l = L;
    	tree[root].r = R;
    	if(L == R){
    		tree[root].sum = a[L];
    		return;
    	}
    	int mid = (L+R)>>1;
    	build(L,mid,root<<1);
    	build(mid+1,R,root<<1|1);
    	pushup(root);
    }
    void update(int L,int R,int al,int ar,int root,long long w){
    	if(R < al || L > ar)return;
    	if(al <= L && R <= ar){
    		tree[root].sum += w*(R-L+1);
    		tree[root].mark += w;
    		return;
    	}
    	pushdown(root);
    	int mid = (L+R)>>1;
    	update(L,mid,al,ar,root<<1,w);
    	update(mid+1,R,al,ar,root<<1|1,w);
    	pushup(root);
    }
    long long query(int L,int R,int al,int ar,int root){
    	if(R < al || L > ar)return 0;
    	if(al <= L && R <= ar)return tree[root].sum;
    	pushdown(root);
    	int mid = (L+R)>>1;
    	return query(L,mid,al,ar,root<<1)+query(mid+1,R,al,ar,root<<1|1);
    }
    int main(){
    	cin>>n>>m;
    	for(int i = 1;i <= n;i++)scanf("%lld",&a[i]);
    	build(1,n,1);
    	for(int i = 1,op,x,y;i <= m;i++){
    		long long k;
    		scanf("%d",&op);
    		if(op == 1)scanf("%d%d%lld",&x,&y,&k),update(1,n,x,y,1,k);
    		if(op == 2)scanf("%d%d",&x,&y),printf("%lld
    ",query(1,n,x,y,1));
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    2019.6.20刷题统计
    36 线程 队列 守护线程 互斥锁 死锁 可重入锁 信号量
    35 守护进程 互斥锁 IPC 共享内存 的方式 生产者消费者模型
    34 进程 pid ppid 并发与并行,阻塞与非阻塞 join函数 process对象 孤儿进程与僵尸进程
    33 udp 域名 进程
    32 粘包 文件传输
    31 socket客户端. 服务器 异常 语法
    30 网络编程
    29 元类 异常
    26 封装 反射 常用内置函数
  • 原文地址:https://www.cnblogs.com/wangyifan124/p/10294433.html
Copyright © 2011-2022 走看看