题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上k。
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数n,m分别表示该数列数字的个数和操作的总个数。

第二行包含n个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来m行每行包含3或4个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间[x,y]内的数每个加上k。
  2. 2 x y:输出区间[x,y]内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入输出样例

输入 #1 复制

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出 #1 复制

11
8
20

说明/提示

保证任意时刻数列中任意元素的和在[-2^63,2^63)内。

对于100%的数据,1<=n,m<=10^5。

【样例解释】

这是一个经典的线段树,曾经让我满脸懵逼的算法,但是真的很好用。(虽然代码有点长)今天我讲讲自己的理解,希望能帮到不会的同学。

一张烂到不能再烂的图片:

这张图片的最低层就是原数组,每个方块下面的数组就是在线段树数组中的位置。先从1开始,如果现在的位置是一个点,就返回这个点的值,否则继续向下查找,然后把这个点的值设定为他左右儿子的和。

一个神奇的操作:

void build(long long int l,long long int r,long long int k)
{
tree[k].l=l;//tree是线段树数组,l和r分别是左右点位置。
tree[k].r=r;
if(l==r)//如果是同一个点,表示到达叶子节点,该输入了。
{
scanf("%lld",&tree[k].zhi);
return ;
}
int mid=(l+r)/2;//分成2段,二分。
build(l,mid,k*2);//一个位置是k*2
build(mid+1,r,k*2+1);//一个位置是k*2+1
tree[k].zhi=tree[k*2].zhi+tree[k*2+1].zhi;//父节点的值相当于2个子节点的和。
}

这就是线段树的初始化。大家可以输出一下tree数组的zhi变量,一定和上图一样,每个节点都等于他的两个子节点。

线段树初始化完了。接下来是查找。

上面的初始化我们让父节点等于他的2个子节点相加,我们就根据这个来求区间查找。具体思想是:如果爸爸超过了范围,就去找儿子,一直向下找,直到找到一个被要求的区间完全包含的后代。然后就把他的值返回,这个方法是绝对不会重复的,因为线段树每层每个节点值只包含在一个空间内。如果爸爸被选择,儿子也就没有必要查下去了。就造就了一个上下层不可能被选,同层不存在重叠的现象。所以这种方法不可能重复。

另外还有一个小小的判断,如果要选区间的开头大于儿子的结尾,或者相反,那这个儿子就没比要查下去了。

说了这么多,该写代码了:

void chazhao(long long int k)//现在的位置
{
if(tree[k].l>=q&&tree[k].r<=h)//被完全包含,q,h,是要查找区间的开头和结尾
{
shu+=tree[k].zhi;//shu是最后的加和。
return ;
}
int mid=(tree[k].l+tree[k].r)/2;//获取子节点的结尾位置。
if(q<=mid)//开头小于左子节点的结尾,左子节点包含一部分。需要查看。
{
chazhao(k*2);
}
if(h>mid)//结尾大于右子节点的开头,右子节点包含一部分。需要查看。
{
chazhao(k*2+1);
}
}

 查找和建树都是这么草率。好好理解一下二分就可以写出来。接下来是(我认为)最难的区间修改,他需要用到一个神奇的东西,叫做懒标记,其意差不多是这个区间包含的值全都要加a,那我就先算出自己需要的值,加上。再定义一个变量,告诉他这个以下全部都要+a,然后就不管了……咕咕咕

当然没这么容易结束,我们以一种现在不用死活不动的态度来处理这个a。只有需要用到这个区间的子区间时,才会把标记下传。懒标记的好处就是避免无用操作,用得到再动。可以毫不夸张的说,没来懒标记的线段树,连暴力都不如。

void down(long long int k)
{
tree[k*2].zhi+=tree[k].f*(tree[k*2].r-tree[k*2].l+1);
tree[k*2].f+=tree[k].f;
tree[k*2+1].zhi+=tree[k].f*(tree[k*2+1].r-tree[k*2+1].l+1);
tree[k*2+1].f+=tree[k].f;
tree[k].f=0;
return ;
}

下降函数,当需要查找一个空间的子节点,但这个空间的懒标记没有清空,就会对子节点的操作产生误差。每个值都加上a的话,整个空间增加的量就是(存的长度*a)。然后这个空间需要继承父亲要增加的值。因为他的子节点一样要加。但我们仍然以现在不用死活不动的态度来处理。也就是说,不主动向下传,只有要用的时候再传。

要判断是否要用,就要在每个函数都加一些东西:

if(tree[k].f!=0){//如果懒标记不为0,说明他的子节点没有加上应该加的数,会导致误判,所以向下传承懒标记。
down(k);
}

如果在查找的时候不包含,就判断。因为他要去下一层了,需要把这一层的懒标记向下移动。

现在就差最后一步,修改。

void xg(long long int k)
{
if(tree[k].l>=q&&tree[k].r<=h)
{
tree[k].zhi+=(tree[k].r-tree[k].l+1)*a;//先改变本身的值
tree[k].f+=a;//懒标记增加。
return;
}
if(tree[k].f!=0)//要去找儿子,但懒标记还有,向下传。
{
down(k);
}
int mid=(tree[k].l+tree[k].r)/2;
if(q<=mid)
{
xg(k*2);
}
if(h>mid)
{
xg(k*2+1);
}
tree[k].zhi=tree[k*2].zhi+tree[k*2+1].zhi;//父节点的值等于左右子节点的和。
return;
}

好了,现在该上完整的代码了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstring>
using namespace std;
long long n,shu,q,h,m,a,l,r,a1;
struct hehe
{
long long l,r,f,w,zhi;
}tree[400005];//数组大小开到n*4比较保险
void build(long long int l,long long int r,long long int k)
{
tree[k].l=l;//tree是线段树数组,l和r分别是左右点位置。
tree[k].r=r;
if(l==r)//如果是同一个点,表示到达叶子节点,该输入了。
{
scanf("%lld",&tree[k].zhi);
return ;
}
int mid=(l+r)/2;//分成2段,二分。
build(l,mid,k*2);//一个位置是k*2
build(mid+1,r,k*2+1);//一个位置是k*2+1
tree[k].zhi=tree[k*2].zhi+tree[k*2+1].zhi;//父节点的值相当于2个子节点的和。
}
void down(long long int k)
{
tree[k*2].zhi+=tree[k].f*(tree[k*2].r-tree[k*2].l+1);
tree[k*2].f+=tree[k].f;
tree[k*2+1].zhi+=tree[k].f*(tree[k*2+1].r-tree[k*2+1].l+1);
tree[k*2+1].f+=tree[k].f;
tree[k].f=0;
return ;
}
void chazhao(long long int k)//现在的位置
{
if(tree[k].l>=q&&tree[k].r<=h)//被完全包含,q,h,是要查找区间的开头和结尾
{
shu+=tree[k].zhi;//shu是最后的加和。
return ;
}
if(tree[k].f!=0){//如果懒标记不为0,说明他的子节点没有加上应该加的数,会导致误判,所以向下传承懒标记。
down(k);
}
int mid=(tree[k].l+tree[k].r)/2;//获取子节点的结尾位置。
if(q<=mid)//开头小于左子节点的结尾,左子节点包含一部分。需要查看。
{
chazhao(k*2);
}
if(h>mid)//结尾大于右子节点的开头,右子节点包含一部分。需要查看。
{
chazhao(k*2+1);
}
}
void xg(long long int k)
{
if(tree[k].l>=q&&tree[k].r<=h)
{
tree[k].zhi+=(tree[k].r-tree[k].l+1)*a;
tree[k].f+=a;
return;
}
if(tree[k].f!=0)
{
down(k);
}
int mid=(tree[k].l+tree[k].r)/2;
if(q<=mid)
{
xg(k*2);
}
if(h>mid)
{
xg(k*2+1);
}
tree[k].zhi=tree[k*2].zhi+tree[k*2+1].zhi;
return;
}
int main()
{
cin>>n>>m;
build(1,n,1);
for(int i=0;i<m;i++)
{
scanf("%lld",&a1);
if(a1==1)
{
scanf("%lld%lld%lld",&q,&h,&a);
xg(1);
}else if(a1==2)
{
scanf("%lld%lld",&q,&h);
shu=0;
chazhao(1);
cout<<shu<<endl;
}
}
return 0;
}

今天的线段树就先讲到这里,大家快去试试吧。

C++ 简单介绍线段树的更多相关文章

  1. 线段树:CDOJ1591-An easy problem A (RMQ算法和最简单的线段树模板)

    An easy problem A Time Limit: 1000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others) Pr ...

  2. 「CQOI2006」简单题 线段树

    「CQOI2006」简单题 线段树 水.区间修改,单点查询.用线段树维护区间\([L,R]\)内的所有\(1\)的个数,懒标记表示为当前区间是否需要反转(相对于区间当前状态),下方标记时懒标记取反即可 ...

  3. 几道简单的线段树入门题 POJ3264&&POJ3468&&POJ2777

    Balanced Lineup Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 40687   Accepted: 19137 ...

  4. ZOJ 3349 Special Subsequence 简单DP + 线段树

    同 HDU 2836 只不过改成了求最长子串. DP+线段树单点修改+区间查最值. #include <cstdio> #include <cstring> #include ...

  5. 洛谷P5057 [CQOI2006]简单题(线段树)

    题意 题目链接 Sol 紫色的线段树板子题??... #include<iostream> #include<cstdio> #include<cmath> usi ...

  6. poj 3468 A Simple Problem with Integers(原来是一道简单的线段树区间修改用来练练splay)

    题目链接:http://poj.org/problem?id=3468 题解:splay功能比线段树强大当然代价就是有些操作比线段树慢,这题用splay实现的比线段树慢上一倍.线段树用lazy标记差不 ...

  7. HDU1556:Color the ball(简单的线段树区域更新)

    http://acm.hdu.edu.cn/showproblem.php?pid=1556 Problem Description N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定 ...

  8. Balanced Lineup(最简单的线段树题目)

    Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 33389   Accepted: 15665 Case Time Limit ...

  9. hdu1556 Color the ball 简单线段树

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1556 简单的线段树的应用 直接贴代码了: 代码: #include<iostream> # ...

随机推荐

  1. [问题解决]coding时修改代码键入前边后边的自动删除

    问题原因:误按下键盘上的Insert键 解决办法:再按一下即可

  2. vc++如何知道cppdlg所关联的对话框?

    vc++ 6.0如何知道cppdlg所关联的对话框? 找a.cpp对应的a.h头文件里面找. https://blog.csdn.net/txwtech/article/details/1020824 ...

  3. 使用Docker构建企业Jenkins CI平台

    在如今的互联网时代,随着软件开发复杂度的不断提高,软件开发和发布管理也越来越重要.目前已经形成一套标准的流程,最重要的组成部分就是持续集成(Continuous Integration,CI)及持续部 ...

  4. WeChair项目Beta冲刺(9/10)

    团队项目进行情况 1.昨日进展    Beta冲刺第九天 昨日进展: 项目开始扫尾 2.今日安排 前端:前端工作已经完成 后端:扫码占座后端测试,实现对超时预约座位下座的功能 数据库:和后端组织协商扫 ...

  5. springboot 之 根据传入参数进行多数据源动态切换

    背景:最近有一个需求是根据app传来的请求参数,根据行政部门编码请求不同地区的数据,之前写的多数据源都是固定某个方法调用指定的dao然后查询不同的数据库,但是这次是需要根据前端传入参数进行动态区分数据 ...

  6. JDK8--06:Stream流

    一.描述 Stream流提供了筛选与切片.映射.排序.匹配与查找.归约.收集等功能 筛选与切片: filter:接收lambda,从流中排除某些元素 limit(n):截断流,使其元素不超过n ski ...

  7. Ticket Game【博弈】

    题目 Monocarp and Bicarp live in Berland, where every bus ticket consists of n digits (n is an even nu ...

  8. Spring Security(一) —— Architecture Overview

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-1/ 「老徐」欢迎转载,保留摘要,谢谢! 1 核心组件 一直以来我都想写一写Spring Secur ...

  9. 断路器Hystrix(Ribbon)

    微服务架构中,根据业务划分成若干个服务,各单元应用间通过服务注册与订阅的方式互相依赖,依赖通过远程调用的方式执行,该方式难以避免因网络或自身原因而出现故障或者延迟,从而并不能保证服务的100%可用,此 ...

  10. 为什么总是无法访问VMware内的web服务?

    除了防火墙的设置,很可能时因为你的Web服务监听的时127.0.0.1地址,构成了本机回环,只能本机访问的原因. 启动服务的时候可以尝试指定hostname为0.0.0.0或者你想监听的IP地址. [ ...