线段树-进阶

上一次我们写的线段树已经可以解决区间查询、单点修改了!可喜可贺

那如果现在我们需要区间修改、区间查询呢?

一般有两种思路:lazytag和标记永久化,lazytag的使用面好像更广一些。

一、lazytag

比如我们现在要修改一个区间,我们可以像查询一样分成若干段,然后分别修改每一段。

那么问题来了,每一段要怎么修改?如果直接修改sum,询问就乱套了。如果暴力修改,最坏复杂度O(n),那还要线段树干嘛(╯‵□′)╯掀桌

那我们这么想,我们修改就不修改整个区间了,而是在区间上维护一个标记。

那我们查询的时候要怎么办呢?我们一路走一路下传标记,就是说把这个点的标记清掉,更新本节点的信息,并把标记传给儿子。

那这样又有一个问题,如果我们修改了一个点的孩子,然后查询的时候只查询到上面的这个点,那信息不就更新不到了吗?

那我们查询的时候还要用儿子节点的信息顺便更新一下本节点的信息。

说了这么多…写起来好像还是蛮简单的。

下面这份代码的码风比较奇怪…就是只建了满二叉树,没有按n来建…代码里的ls和rs表示区间的左右端点…

//codevs1082 区间加一个数 区间查询和
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
#include <math.h>
#include <set>
#include <map>
using namespace std;
int n;
typedef long long ll;
#define SZ 555555
int M=262144,M2=M+M,ls[SZ],rs[SZ];
ll sum[SZ],tag[SZ];
void build()
{
    for(int i=M+1;i<=M+M;i++) ls[i]=rs[i]=i-M;
    for(int i=M-1;i;i--) ls[i]=ls[i+i], rs[i]=rs[i+i+1], sum[i]=sum[i+i]+sum[i+i+1];
}
void pd(int x)
{
    if(tag[x])
    {
        sum[x]+=tag[x]*(rs[x]-ls[x]+1);
        if(x+x<=M2) tag[x+x]+=tag[x], tag[x+x+1]+=tag[x];
        tag[x]=0;
    }
}
void upd(int x)
{
    pd(x+x); pd(x+x+1);
    sum[x]=sum[x+x]+sum[x+x+1];
}
void edit(int x,int ql,int qr,int v)
{
    if(x>M2||ql>qr) return;
    pd(x);
    if(ql==ls[x]&&qr==rs[x]) {tag[x]+=v; return;}
    int mid=ls[x]+rs[x]>>1;
    edit(x+x,ql,min(qr,mid),v);
    edit(x+x+1,max(mid+1,ql),qr,v);
    upd(x);
}
ll gsum(int x,int ql,int qr)
{
    if(x>M2||ql>qr) return 0;
    pd(x);
    if(ql==ls[x]&&qr==rs[x]) return sum[x];
    int mid=ls[x]+rs[x]>>1;
    ll ans=gsum(x+x,ql,min(qr,mid))+gsum(x+x+1,max(mid+1,ql),qr);
    upd(x); return ans;
}
int q,a,b,c;
char buf[3];
void readdata()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&sum[i+M]);
    scanf("%d",&q); build();
    while(q--)
    {
        scanf("%s",buf);
        if(buf[0]==‘2‘)
        {
            scanf("%d%d",&a,&b);
            printf("%lld\n",gsum(1,a,b));
        }
        else
        {
            scanf("%d%d%d",&a,&b,&c);
            edit(1,a,b,c);
        }
    }
}
int main()
{
    readdata();
}

二、标记永久化

你可能会觉得:lazytag使用起来很方便,感觉也很强大,为啥还要什么标记永久化?

等你学了主席树你就明白了 反正多学总没有什么问题233

标记永久化就是如果我们要修改一个区间,还是在区间上打一个标记,但是不下传!

——啥,不下传如何保证正确性?

我们在修改的路径上更新这个区间的和,然后在返回和的时候把标记累加进去,这样就可以保证正确性了。

//codevs1082 区间加一个数 区间查询和
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
#include <math.h>
#include <set>
#include <map>
using namespace std;
int n;
typedef long long ll;
#define SZ 555555
int MAXN=524288;
ll sum[SZ],tag[SZ];
void edit(int x,int ql,int qr,int v,int l,int r)
{
    if(x>MAXN||ql>qr||l>r) return;
    if(ql==l&&qr==r) {tag[x]+=v; return;}
    sum[x]+=(qr-ql+1)*v;
    int mid=l+r>>1;
    edit(x+x,ql,min(qr,mid),v,l,mid);
    edit(x+x+1,max(mid+1,ql),qr,v,mid+1,r);
}
ll gsum(int x,int ql,int qr,int l,int r)
{
    if(x>MAXN||ql>qr) return 0;
    if(ql==l&&qr==r) return sum[x]+tag[x]*(qr-ql+1);
    int mid=l+r>>1;
    return gsum(x+x,ql,min(qr,mid),l,mid)+gsum(x+x+1,max(mid+1,ql),qr,mid+1,r)+tag[x]*(qr-ql+1);
}
int q,a,b,c;
char buf[3];
void readdata()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int a=i,b; scanf("%d",&b);
        edit(1,a,a,b,1,n);
    }
    scanf("%d",&q);
    while(q--)
    {
        scanf("%s",buf);
        if(buf[0]==‘2‘)
        {
            scanf("%d%d",&a,&b);
            printf("%lld\n",gsum(1,a,b,1,n));
        }
        else
        {
            scanf("%d%d%d",&a,&b,&c);
            edit(1,a,b,c,1,n);
        }
    }
}
int main()
{
    readdata();
}

我相信你学完这两种方法,线段树的题都可以随手秒啦!

时间: 2024-10-24 10:20:03

线段树-进阶的相关文章

CSU-ACM集训-模板-线段树进阶

A题 原CF 438D The Child and Sequence 题意 给一串数字,m次操作,1.区间查询:2.区间取模:3.单点修改 基本思路 考虑到模如果大于区间的最大值,则取模没有意义.若小于则向下查询并修改,考虑到一个数每次取模最多为原数的\(1/2\),则可认为修改次数不超过\(\log{2}{n}\) 时间复杂度为\(O(n\log{2}{n}\log{2}{n})\) #include<bits/stdc++.h> #define FOR(i,a,b) for(int i=a

线段树进阶

T1:高速公路 题干: $Y901$ 高速公路是一条重要的交通纽带,政府部门建设初期的投入以及使用期间的养护费用都不低,因此政府在这条高速公路上设立了许多收费站. $Y901$ 高速公路是一条由 $N-1$ 段路以及 $N$ 个收费站组成的东西向的链,我们按照由西向东的顺序将收费站依次编号为1-N,从收费站 i 行驶到 i+1 (或从 i+1 行驶到 i )需要收取 V_i 的费用.高速路刚建成时所有的路段都是免费的. 政府部门根据实际情况,会不定期地对连续路段的收费标准进行调整,根据政策涨价或

线段树进阶之落花成泥

————————————————————————相识,是在那么不经意的瞬间.我还远远不够,远远,远远,远远不够啊.加油呀! foundation : 1.异或,英文为exclusive OR,缩写成xor. 异或(xor)是一个数学运算符.它应用于逻辑运算.异或的数学符号为"⊕",计算机符号为"xor".其运算法则为:a⊕b = (¬a ∧ b) ∨ (a ∧¬b). 如果a.b两个值不相同,则异或结果为1.如果a.b两个值相同,异或结果为0. 异或也叫半加运算,其

线段树与树状数组草稿

http://bestcoder.hdu.edu.cn/contests/contest_showproblem.php?cid=604&pid=1002 Dylans loves sequence Accepts: 249 Submissions: 806 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others) Problem Description Dylans is given N

线段树--从入门到精通

线段树,强大的数据结构,用处也是比较广的. 首先,我们要明白线段树是个啥? 线段树,线段嘛,有左右端点,那么它当然可以代表一个区间,那么区间上的好多事情都可以用它来搞,比如:区间加,区间乘,区间求和. 首先让我们先看个线段树的模型. 如图,这就是一棵线段树的模型. 圈内的点表示这是第几个点,红色表示这个点表示的区间范围. 每个点和它的左右两个儿子的编号是有一定的关系的: 点N,它的左儿子编号为N$\times$2,右儿子编号为N$\times$2+1. 线段树支持单点修改,区间修改,单点查询,区

「模板」线段树静态开点(单点+区间修改)、动态开点

相关讲解资料: 树状数组:https://blog.csdn.net/qq_34374664/article/details/52787481 (线段树预备) 线段树讲解: 初学版:https://blog.csdn.net/zearot/article/details/52280189 进阶完整版:https://www.cnblogs.com/AC-King/p/7789013.html 代码: 完整注释模板一张,参(chao)考(xi)楼上的博客 #include<iostream> #

超全面的线段树:从入门到入坟

超全面的线段树:从入门到入坟 \(Pre\):其实线段树已经学了很久了,突然线段树这个数据结构比较重要吧,现在想写篇全面的总结,帮助自己复习,同时造福广大\(Oier\)(虽然线段树的思维难度并不高).本篇立志做一篇最浅显易懂,最全面的线段树讲解,采用\(lyd\)写的<算法竞赛进阶指南>上的顺序,从最基础的线段树到主席树,本篇均会涉及,并且附有一定量的习题,以后可能会持续更新,那么现在开始吧! 目录 更新日志 线段树想\(AC\)之基本原理(雾*1 线段树想偷懒之懒标记(雾*2 线段树想应用

浅谈二维线段树的几种不同的写法

目录 参考文献 参考文献 暴力写法 二叉树 四叉树 树套树写法1 参考文献 四叉树 树套树 以及和zhoufangyuan巨佬的激烈♂讨论 参考文献 大家好我口糊大师又回来了. 给你一个\(n*n\)矩阵,然后让你支持两种操作,对子矩阵加值和对子矩阵查和. 暴力写法 对于每一行开一个线段树,然后跑,时间复杂度\(n^2logn\). 优点: 代码较短 较为灵活 缺点: 常数大 容易卡 二叉树 我们对于平面如此处理,一层维护横切,一层竖切. 当然,这个做法也是\(n^2logn\)的,卡法就是任意

数据结构:线段树

摘自<算法竞赛进阶指南>. 线段树是一种基于分治思想的二叉树结构,用于在区间上进行信息统计. 线段树的基本特征:1.线段树的每个节点都代表一个区间.2.线段树具有唯一的根节点,代表的区间是整个统计范围,如[1,N].3.线段树的每个叶节点都代表一个长度为1的元区间[x,x].4.对于每个内部节点[l,r],它的左子节点是[l,mid],右子节点是[mid+1,r],其中mid=(l+r)/2(向下取整). 线段树的节点编号方法:“父子二倍”节点编号法1.根节点编号为1.2.编号为x的节点的左子