题目来源:https://www.luogu.org/problemnew/show/3372,侵删。

题目描述

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

1.将某区间每一个数加上x

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
说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=8,M<=10

对于70%的数据:N<=1000,M<=10000

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

(数据保证在int64/long long数据范围内)

样例说明:

 
  对于较小的数据,当然可以用数组模拟,代码简单人见人爱。但针对较大数据就会果断GG,所以需要使用线段树,线段树在处理较大数据时有奇效,时间复杂度为O(logn),但占用空间也相对较大,达到4n。
   
  (1)建树
  线段树的主要思想是二分,将一个大区间不停二分直到成为一个单点,如图:
  
  在对区间进行操作时,只需要对大区间进行操作,不用下到每个叶结点,查询时同理。
  建树时,我们将线段树储存为线性结构,又因为该线段树为完全二叉树结构,所以对于每一个父节点x[v],x[v×2]和x[v×2+1]为其子节点。而对于每一个元素,需要记录的变量有:左区间(le),右区间(ri)。此外,根据题意,还应记录该区间的元素和(sum)以及区间操作的加数(ad),所以定义结构体如下:

struct sd{int le,ri;long long sum,ad;sd(){memset(this,0,sizeof(this));}
};

  其中,this指向该结构体中的元素。memset语句则将该结构体中的所有元素初始化为0。
  记录加数(ad)的目的是延后对区间加法的运算,在查询时再调用。另外,该加数针对的是其子节点的加数,而非自己的。
  然后就是建树操作了,将子节点的区间和合并到父节点上,如果该节点已经是最底层叶节点(即左右区间相等),那么就从读入的初始数据中赋值。建树时我们采用递归操作,每次都进行二分,遇到叶结点时回溯,合并区间。
  注意:对二分点(mid)所属区间的划分,否则会莫名RE,在这里博主定义左区间为[le,mid],右区间为(mid,ri]。在后面进行区间加/查询时也要遵守建树时的定义。
  代码如下:

void up(int v)
{tree[v].sum=tree[v*2].sum+tree[v*2+1].sum;//合并自己子区间的和
}
void build(int v,int le,int ri)
{tree[v].le=le;tree[v].ri=ri;//确定区间范围if(le==ri){tree[v].sum=x[le];return;//当递归到叶结点时,回溯}int mid=(le+ri)/2;build(v*2,le,mid);build(v*2+1,mid+1,ri);//递归二分up(v);//合并子区间
}

  在进行区间加的操作时,同样采用递归二分进行赋值,遇到被完全包含的区间时就停止递归,将该区间的区间和进行修改,然后将加数传递给子区间

void pushdown(int t,int le,int ri)
{//将加数传递给子区间int mid=(le+ri)/2;tree[t*2].ad+=tree[t].ad;tree[t*2+1].ad+=tree[t].ad;//传递加数tree[t*2].sum+=(mid-le+1)*tree[t].ad;tree[t*2+1].sum+=(ri-mid)*tree[t].ad;//修改区间和tree[t].ad=0;//传递完毕后将父区间的加数归零
}
void add(int t,int le,int ri,int ll,int rr,int ad)
{if(ll<=le&&ri<=rr){//如果遇到完全包含的区间,更改其加数与区间和后回溯tree[t].ad+=ad;tree[t].sum+=(ri-le+1)*ad;return;}if(tree[t].ad!=0){//如果该区间有加数,将加数传递给子区间,为后面的二分递归做准备pushdown(t,le,ri);}int mid=(le+ri)/2;if(ll<=mid){//没有完全包含则二分递归继续进行区间加add(t*2,le,mid,ll,rr,ad);}if(rr>mid){add(t*2+1,mid+1,ri,ll,rr,ad);}up(t);//更改父节点
}

  然后就是查询操作,同样递归二分。参数从左到右依次为:当前查询的节点编号,当前查询到的左、右区间以及要求查询的左、右区间。

long long qsum(int t,int le,int ri,int ll,int rr)
{if(ll<=le&&ri<=rr){//如果该区间被完全包含,返回该区间的和return tree[t].sum;}if(tree[t].ad!=0){//和区间加一样,更改子结点的值为二分递归查找做准备pushdown(t,le,ri);}int mid=(le+ri)/2;long long s=0;if(ll<=mid){//二分查找s=qsum(t*2,le,mid,ll,rr);}if(mid<rr){s+=qsum(t*2+1,mid+1,ri,ll,rr);}//返回左右两个区间查询得到的结果之和return s;
}

  基本的函数都已经介绍完毕,在调用函数进行区间加/查询时,需要将当前查询的节点编号(t),当前查询到的左(le)、右(ri)区间都设为最大的区间(即t=1,le=1,ri=n),剩下的都交给递归解决。
  最后贴上博主丑陋的代码:

#include<cstdio>
#include<cstring>
using namespace std;
struct sd{int le,ri;long long sum,ad;sd(){memset(this,0,sizeof(this));}
};
long long x[100005];
sd tree[400005];
void up(int v)
{tree[v].sum=tree[v*2].sum+tree[v*2+1].sum;
}
void build(int v,int le,int ri)
{//建树tree[v].le=le;tree[v].ri=ri;if(le==ri){tree[v].sum=x[le];return;}int mid=(le+ri)/2;build(v*2,le,mid);build(v*2+1,mid+1,ri);up(v);
}
void pushdown(int t,int le,int ri)
{int mid=(le+ri)/2;tree[t*2].ad+=tree[t].ad;tree[t*2+1].ad+=tree[t].ad;tree[t*2].sum+=(mid-le+1)*tree[t].ad;tree[t*2+1].sum+=(ri-mid)*tree[t].ad;tree[t].ad=0;
}
void add(int t,int le,int ri,int ll,int rr,int ad)
{//区间加if(ll<=le&&ri<=rr){tree[t].ad+=ad;tree[t].sum+=(ri-le+1)*ad;return;}if(tree[t].ad!=0){pushdown(t,le,ri);}int mid=(le+ri)/2;if(ll<=mid){add(t*2,le,mid,ll,rr,ad);}if(rr>mid){add(t*2+1,mid+1,ri,ll,rr,ad);}up(t);
}
long long qsum(int t,int le,int ri,int ll,int rr)
{//查询区间和if(ll<=le&&ri<=rr){return tree[t].sum;}if(tree[t].ad!=0){pushdown(t,le,ri);}int mid=(le+ri)/2;long long s=0;if(ll<=mid){s=qsum(t*2,le,mid,ll,rr);}if(mid<rr){s+=qsum(t*2+1,mid+1,ri,ll,rr);}return s;
}
int main()
{int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){scanf("%d",&x[i]);//读入初始数据}build(1,1,n);//建树for(int i=1;i<=m;i++){int p;int x,y,k;scanf("%d",&p);if(p==1){scanf("%d%d%d",&x,&y,&k);add(1,1,n,x,y,k);//参数都设为最大区间}else{scanf("%d%d",&x,&y);printf("%lld\n",qsum(1,1,n,x,y));//同上}}return 0;
}

  线段树作为一种高效的数据结构,需要4n的空间作为牺牲(ps:请一定把数组开的够大)但是相应的,面对更大的数据(>=5000000)时内存炸裂依然会GG,那么针对这种情况的优化以(lǎo)后(zi)再(bú)讲(huì)。另外,针对线段树的维护也是一件头疼的事,要维护的量往往不会像例题一样明显,同时也会更加复杂,需要靠大家自己的修为了。

基(là)础(jī)线段树详解相关推荐

  1. 【学习笔记】线段树详解(全)

    [学习笔记]线段树详解(全) 和三个同学一起搞了接近两个月的线段树,头都要炸了T_T,趁心态尚未凉之前赶快把东西记下来... [目录] [基础]作者:\((Silent\)_\(EAG)\) [懒标记 ...

  2. 线段树详解 (原理,实现与应用)

    线段树详解 By 岩之痕 目录: 一:综述 二:原理 三:递归实现 四:非递归原理 五:非递归实现 六:线段树解题模型 七:扫描线 八:可持久化 (主席树) 九:练习题 一:综述 假设有编号从1到n的 ...

  3. 如何在vs中创建r树索引代码_线段树详解与实现

    此篇文章用于记录<玩转数据结构>课程的学习笔记 什么是线段树 线段树也被称为区间树,英文名为Segment Tree或者Interval tree,是一种高级的数据结构.这种数据结构更多出 ...

  4. 线段树详解(转)这个博客很棒~

    作者:Dong | 可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明 网址:http://dongxicheng.org/structure/segment-tree/ 1.概述 线 ...

  5. c语言线段树建树程序,c语言数据结构之线段树详解;例题:校门外的树(poj2808或者vijos1448)...

    线段树:它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个结点. 也就是说线段树的每一个结点对应一个区间,其中根节点对应区间[1,n] 对于线段树中的每一个非叶子节点[a,b],它的左儿子 ...

  6. 数据结构_线段树 详解+模板

  7. Android 驱动(12)---Linux DTS(Device Tree Source)设备树详解

    Linux DTS(Device Tree Source)设备树详解 Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇) Linux DTS(Device Tr ...

  8. 高通平台msm8953 Linux DTS(Device Tree Source)设备树详解之二(DTS设备树匹配过程)

    本系列导航: 高通平台8953  Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇) 高通平台8953 Linux DTS(Device Tree Source ...

  9. 高通平台8953 Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇)

    本系列导航: 高通平台8953  Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇) 高通平台8953 Linux DTS(Device Tree Source ...

  10. 主线剧情-番外02-设备树详解

    设备树详解 本文 续接 <[主线剧情03]NXP i.MX 系列 u-boot 移植基础详解> 一文中移植过程小节中有关设备树的内容. 编辑整理 By Staok,如有错误恭谢指出,侵删. ...

最新文章

  1. python 获取你电脑纯文本文档内容!解决IndentationError: expected an indented block报错!
  2. @value 注入静态属性_SpringBoot使用@Value读取属性值
  3. (最终作业)面向对象先导课课程总结
  4. aspxgridview 增加行号
  5. [深度学习] FM FFM 算法基本原理
  6. android play gif,Play.gif image in android without using webview
  7. mac连接手机 vm_使用mac ssh连接谷歌云vm实例
  8. 1. ansible-playbook 变量定义与引用
  9. 短视频秒播优化实践(二)
  10. GridView中设置DataFormatString无效的解决方法.
  11. Shiro(一)——Shiro概述
  12. python 抓取 快代理-国内普通代理 IP
  13. 卡尔玛滤波的原理说明
  14. 039 我的技术成长历程
  15. 2019年苏大计算机考研872真题及解析
  16. eyoucms相关问题总结
  17. win10服务和控制器应用CPU占用过高
  18. 3年功能测试经验,面试想拿到15k很难吗?
  19. 电脑贴的标签 MFG YR是什么意思
  20. C++——VS2019调整字体和主题

热门文章

  1. 中查询每个班成绩前三_重磅 2020卫生资格中初级考试成绩今天公布了
  2. 删除html注释 python,用Python提取HTML源码中的注释与去掉注释
  3. Codeforces Round #129 (Div. 1)E. Little Elephant and Strings
  4. Oozie 安装及 examples app 的使用
  5. 【poj1743-Musical Theme】不可重叠最长重复子串-后缀数组
  6. cocos2d-x由Jni实现Java与C++打电话给对方
  7. Senparc.Weixin.MP SDK 微信公众平台开发教程(四):Hello World
  8. hibernate 基本步骤 一
  9. c# 操作FTP文件类
  10. nginx/windows: nginx多虚拟主机配置