线段树模板详解
来源:互联网 发布:诺基亚n8刷windows 编辑:程序博客网 时间:2024/06/15 18:54
原题:洛谷:https://www.luogu.org/problem/show?pid=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数据范围内
代码解析已经在代码中呈现!!!
//线段树,初学者适用 //与二叉堆性质相似a的左儿子a[2*i]右儿子a[2*i+1],线//段树是将每个区间[l,r]分解成[l,m],[m+1,r],m=(l+r)/2//直到l==r #include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#define maxn 1000000using namespace std;int x,y,k;long long add[maxn]/*表示每个点的标记(惰性标记)*/;long long a[maxn];//存储最初的数据 struct node{ int left,right; long long sum;//记录该条线段出现的次数 }tree[maxn];//保存每个点左右端点和极值//数组模拟,空间开到4倍防止访问越界 void buildtree(int i,int l,int r){//构造线段树 tree[i].left=l; tree[i].right=r; if(l==r){//找到叶子节点并且赋值 tree[i].sum=a[l]; return; }//如果该点是叶节点 int mid=(l+r)>>1; buildtree(i<<1,l,mid);//左移一位(二进制) //左子树 buildtree((i<<1)|1,mid+1,r);//右子树 //按位或eg:3|2=0011|0010=0011 tree[i].sum=tree[i<<1].sum+tree[(i<<1)+1].sum;//回溯维护区间和 }void pushdown(int i){//下放惰性标记 int lc=i<<1;; int rc=(i<<1)+1; tree[lc].sum+=(tree[lc].right-tree[lc].left+1)*add[i]; tree[rc].sum+=(tree[rc].right-tree[rc].left+1)*add[i]; add[lc]+=add[i]; add[rc]+=add[i]; add[i]=0;//此处下放标记 }void update(int i,int x,int y,long long k){//区间修改 int lc=i<<1; int rc=(i<<1)|1; if(tree[i].left>y || tree[i].right<x){ return;//如果此区间完全无关 } if(x<=tree[i].left && tree[i].right<=y){ tree[i].sum+=(tree[i].right-tree[i].left+1)*k;//加至此处就不继续往下加 add[i]+=k;//存放惰性标记 } //如果此处是完全有关区间 else{ if(add[i]){ pushdown(i);//下放惰性标记 } update(lc,x,y,k); update(rc,x,y,k); tree[i].sum=tree[lc].sum+tree[rc].sum; }//二分添加 } long long find(int i,int x,int y){ int lc=i<<1; int rc=(i<<1)+1; if(x<=tree[i].left && tree[i].right<=y){ return tree[i].sum; }//当前节点的区间完全被目标区间包含 if(tree[i].left>y || tree[i].right<x){ return 0; }//完全在左子树中or右子树 if(add[i]){ pushdown(i);//下放惰性标记 } return find(lc,x,y)+find(rc,x,y);//目标区间左右都有分布 }//重点详解: //void pushdown(int);//下放惰性标记 //此处标记指的是惰性标记,实际上是让子节点暂时处于//不更新状态等到用到的时候在做更新而区间加时要把和//那个区间有关的区间全部价值,不然不配合输出//超时了就要优化。//你想啊,虽然x~y区间要增加一个值,难道这个区间就一//定要用吗?//如果区间值增加了但后来没有询问,我们一开始为什么//要增加呢?//正如背古文,如果考试不考,我们为什么要背呢?//所以lazy思想就怎么产生了。//lazy,就是懒嘛,就是先不被古文,等到考试要考了再//去背嘛;//先不增加区间,等到询问时在去增加嘛;//我们可以搞一个add数组,专门把编号为num的区间要加//的值记录下来//如果要用了,再给线段树num加上v[num]的值,再把//add[num]//传给v[num*2]和v[num*2+1];然后v[num]清零//“如果要用了”这一步用一个clean函数实现//void buildtree(int,int,int);//构造线段树//void update(int,int,int,long long);//区间修改//1,起始区间,终止区间,修改值 //long long find(int,int,int);//区间查询 //1,区间起始值,区间终止值 int main(){ int n,m,flag; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); } buildtree(1,1,n);//构造线段树 for(int i=0;i<m;i++){ scanf("%d",&flag); if(flag==1){ scanf("%d%d%lld",&x,&y,&k); update(1,x,y,k); } if(flag==2){ scanf("%d%d",&x,&y); printf("%lld\n",find(1,x,y)); } } return 0; } //假设线段树处理的最大长度是n,那么总结点数不超过2*n//线段树分解数量级:线段树能把任意一条长度为M的线段分为//不超过2log(M)条线段,很大的数一下子变小,使得线段树查//询与修改复杂度都在O(log2(n))范围内能得以解决//线段树是一棵二叉树,深度约为log2n左右//所以线段树空间消耗O(n),由于他的深度性质,解决问题效率//更高
阅读全文
1 0
- 线段树模板+详解
- 线段树模板详解
- 线段树 各种模板(详解)
- hdu 1754 I Hate It【线段树入门+模板详解】
- 线段树详解(洛谷模板题)
- ACM 线段树模板(模板)
- 线段树模板
- hdu_1166_线段树模板
- 线段树模板
- 线段树模板 poj2777
- 线段树模板
- 线段树模板
- 线段树-模板
- 线段树模板
- 线段树模板
- 线段树模板
- Hdu1166-- 线段树模板
- 线段树模板
- 写时拷贝
- 大数据背景下,DBA工作模式将会产生怎样的改变
- 有序广播与发短信
- 图像抠图算法学习
- Python 操作MySql数据库
- 线段树模板详解
- Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- 简化版的三木运算符含义
- Tomcat7中的目录作用
- Declarative Caching Services for Spring声明式缓存服务
- 注册表格
- 【接口】-MQ消息队列
- 用PHP开发自己的独立博客(一)——概述
- 英国签证|英国移民|英国签证移民|最佳签--英国最佳签证平台--全英专业签证移民律师同台竞标