线段树学习心得 poj3468
来源:互联网 发布:小猪cms微电商系统 编辑:程序博客网 时间:2024/05/18 01:59
2016.5.10 神奇的博主回来了
今天,当然讲的就是加强版的堆,其实算线段树的说。。。
(还有你们看过的留留言好不好呀,谢谢了)
---------------------------
今天说的就是[[[加强版!!!]]]
内容大致就是成段更新,区间求和
也就是说,会用到高大上的lazy tag,那么lazy tag有多lazy呢?神奇的是,如果每次区间全都加一个数,然后每次全都从下面到顶走一遍的话,妥妥的就炸。所以说,利用lazy tag标记的话,其实就是如果有增加的话,只是做一个标签,在有必要的时候再调整,可以大幅缩小运行需要。
因为需要,博主选择一段一段的来写,当然会在结尾贴上完整代码。(所谓的最后一排/最顶上的,看不懂的话,请在上面的博文中找图)
-----------------------------
因为是一段一段的,就不加注释符了。
首先,是建树
struct Node
{
int l,r;
l所表示的是左子的管辖范围就是说能管的区间的最左端
r所表示的是右子的管辖范围就是说能管的区间的最右端
int sum2,lazy;
sum也就是左子右子所管辖的sum的和
lazy也就是lazy tag
为了便于下面的阅读,我将在这里简单介绍一下<<&>>
首先,<<代表的是二进制移位(往左移位) >>代表的是二进制 往右移位,后面跟的数字就是移位的数。那移位又能干什么呢?比如:
2的二进制是00010是吧
移位一位 00100就是4
移位两位 01000就是8
移位三位 10000就是16
可以看出往左移位1位是乘2,2位是乘4,3位呢,就是乘8等等,说是用起来比乘法要快一些。
反之就是一次除二
} tree[N<<2]; 不是乘2,是乘4!理论上乘3就够了,但是多一点总没坏处,只要不炸就行。
下面就是建树了。
我是潇洒哥,潇洒到根本不处理原数,直接用sum。其实最下面的sum就是原数所以其实更便捷了。
void build(int l,int r,int rt)
这个建树时使用的是递归建树。
{
tree[rt].l = l;
tree[rt].r = r;
完整程序中是build(1,n,1); 当然,不可能全都管辖范围最左端全是1,右端也不可能全是n,这就是为什么递归,当l和r都是初始的1和n时,那么我们在处理的一定是顶端第一个,那么顶端第一个的左端右端就是1和n。
tree[rt].lazy=0;
lazy置空,因为还没有涉及到成段更新的问题,初始化一下
if(l == r)
{
如果说左端和右端一样了,那么就说明是最下面一排的了,那么就要开始输入处理了。
scanf("%d",&sum[rt]);
输入sum,因为最后一排的管辖总和就是自己本身的数
return ;
}
build(lson);
递归左子,你一定会想,三个参数为啥就一个了。。。这里容我解释一下,前面我用了宏
(#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1)
当然,前面<<和>>也做了相应解释,在这里与或非中的或(|)为什么代表+1,我也不是很懂,所以如果有大神指点一下,谢谢。
乘2是左子,乘2+1是右子。涉及到位运算的相关知识,如果说是1|1,那么肯定答案还是1。但是它前面带着一个左移,那么最后一位一定是0,那么或1一定是+1。附上相关知识链接:http://baike.baidu.com/link?url=8gk3GFWZb0DQexRjbrdMjcRkept4HNaaR1EJ4HvMbgLm5COvKWmXPDoNtZFFayzKcS1mR1YnoKX_HHNd9iVTDq
build(rson);
PushUp(rt);
这个PushUp的作用,接下来揭晓。
}
-----------------------------
下面我会选择先把易懂的查询写完。
int query(int l,int r,int rt)
还是递归,我似乎太喜欢递归了吧。。。
{
if(l == tree[rt].l && r == tree[rt].r)
如果说正好的话
{
return tree[rt].sum2;
那答案就是当前的和
}
PushDown(rt,tree[rt].r - tree[rt].l + 1);
这个PushDown的作用,接下来揭晓。
int res = 0;
这个res就是最后的答案。
int m=(tree[rt].l+tree[rt].r)/2;
m所代表的就是一个分界线
就是说,比如说从3到5查询
一共5个数
最开始的一定是1,所以左子是1,右子是5
m=(1+5)/2=3
就是说跟快排(快速排序http://baike.baidu.com/link?url=zvoAGAi9YDykOgfvG__HCoAHVHDHoG8GP1uB9FyKwGpz3qYnRJjPPs0ZEXK1BYPtlkESo2_57WQDokkERB3tbjDLltgx4_BauWtkelIhGFbvosfCuh3vmuMAti6clzgjBrg5CvGC1AXfFS-T8EIGTb0mAzJUOFRLUvFf2WW9ex90V68KasgStbm0i1wmbrZj
可以了解一下,也很有趣,也是从中间平均分)差不多,从中间分开一下。
if(r <= m) res += query(l,r,rt<<1);
如果说右子还在m的左面,也就说所有的全部都在左边的话,那就处理左子
else if(l > m) res += query(l,r,rt<<1|1);
如果说左子还在m的右面,也就说说有的全部都在右面的话,那就处理右子
else
{
res += query(l,m,rt<<1);
res += query(m+1,r,rt<<1|1);
有左有右的话,就求两边的和
}
return res;
返回结果
}
-----------------------------
因为在build里面先出现的是PushUp,所以也没什么理由,任性决定先写PushUp。
void PushUp(int rt)
数据上推,也就是说(找图看)上面的数据
{
tree[rt].sum2=tree[2*rt].sum2+tree[2*rt+1].sum2;
当前的sum就是左子右子sum的和,和上面博文中的max差不太多
}
-----------------------------
void PushDown(int rt,int m)
下发数据,m是左右子区间长度
{
if(tree[rt].lazy==0) return;
如果说没有要加的lazy那就省事了
tree[rt*2].lazy+=tree[rt].lazy;
有的话,下发给左子
tree[rt*2+1].lazy+=tree[rt].lazy;
下发给右子
tree[rt*2].sum2+=tree[rt].lazy*(m-(m/2));
它左子加的和,一定是它能管到的数量的一半再乘上lazy
因为二叉树左子管到的一定是一半,而每个都是加lazy
但是和左子不同的是,虽然说在数学的角度,(m-(m/2))和(m/2)似乎是一样的,但是说如果是单数这样的话,就会有误差,当然误差会像滚雪球一样越滚越大,所以说要特殊处理一下。
tree[rt*2+1].sum2+=tree[rt].lazy*(m/2);
右子不同。
tree[rt].lazy=0;
必须清空!!!看似微小的错误往往是致命的!如果钥匙没有置空的话,就会一直循环下去!!!一定要注意啊!
}
-----------------------------
还剩一个update
void update(int c,int l,int r,int rt)
{
又是递归。。。big boss来啦,就是所谓的成段更新
首先解决三个参数,第一个就是更新所加的数值,第二个第三个就是成段更新的范围,第4个rt当然就是当前位置不用多说
if(tree[rt].l == l && r == tree[rt].r)
如果位置正好
{
tree[rt].lazy+=c;
当前位置的lazy直接就是更新所加的数
tree[rt].sum2+=(int)c * (r-l+1);
当然这个跟上面PushDown中的一模一样,那为什么还要加呢?
当然这个问题愚蠢的楼主想了一会,本来就要改代码了,发现其实如果在这种情况下根本不会调用PushDown,所以并不存在重复的情况,可以安心使用。
return;
递归须谨慎啊!debug伤不起。。。
}
PushDown(rt,tree[rt].r - tree[rt].l + 1);
记得处理完把数据下发一下。
int m=(tree[rt].l+tree[rt].r)/2;
m还是分界
if(r <= m) update(c,l,r,rt<<1);
其实这一段就跟query没什么区别啦,只不过是更新而已,判断条件都没变呢,还是自己去看吧
else if(l > m) update(c,l,r,rt<<1|1);
else
{
update(c,l,m,rt<<1);
update(c,m+1,r,rt<<1|1);
}
PushUp(rt);
上推一下数据。
}
-----------------------------
主函数非常简单,在这里也不再赘述,当然,前面所答应的完整代码是一定会放的,首先,我找到了一道poj3468,先分析一下题目:
A Simple Problem with Integers
Time Limit: 5000MS Memory Limit: 131072K
Total Submissions: 89915 Accepted: 27990
Case Time Limit: 2000MS
Description
You have N integers, A1,A2, ... ,AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.
Input
The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1,A2, ... ,AN. -1000000000 ≤Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
"C a b c" means adding c to each of Aa,Aa+1, ... ,Ab. -10000 ≤c ≤ 10000.
"Q a b" means querying the sum of Aa,Aa+1, ... ,Ab.
Output
You need to answer all Q commands in order. One answer in a line.
Sample Input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output
4
55
9
15
Hint
The sums may exceed the range of 32-bit integers.
Source
POJ Monthly--2007.11.25, Yang Yi
这道题所讲的就是成段更新,区间求和。Q是区间查询,求和。C是成段更新。值得注意的是,“The sums may exceed the range of 32-bit integers.”,用int要炸,但是poj的编译系统只认__int 64,Xcode中的lld似乎是不行的。
代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 100005;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
struct Node
{
int l,r;
__int64 sum,lazy;
int mid()
{
return (l+r)>>1;
}
} tree[N<<2];
void PushUp(int rt)
{
tree[rt].sum=tree[2*rt].sum+tree[2*rt+1].sum;
}
void PushDown(int rt,int m)
{
if(tree[rt].lazy)
{
tree[rt*2].lazy+=tree[rt].lazy;
tree[rt*2+1].lazy+=tree[rt].lazy;
tree[rt*2].sum+=tree[rt].lazy*(m - (m>>1));
tree[rt*2+1].sum+=tree[rt].lazy*(m>>1);
tree[rt].lazy=0;
}
}
void build(int l,int r,int rt)
{
tree[rt].l = l;
tree[rt].r = r;
tree[rt].lazy=0;
if(l == r)
{
scanf("%I64d",&tree[rt].sum);
return ;
}
int m=tree[rt].mid();
build(lson);
build(rson);
PushUp(rt);
}
void update(int c,int l,int r,int rt)
{
if(tree[rt].l == l && r == tree[rt].r)
{
tree[rt].lazy+=c;
tree[rt].sum+=(int)c * (r-l+1);
return;
}
if(tree[rt].l == tree[rt].r) return;
PushDown(rt,tree[rt].r - tree[rt].l + 1);
int m = tree[rt].mid();
if(r <= m) update(c,l,r,rt<<1);
else if(l > m) update(c,l,r,rt<<1|1);
else
{
update(c,l,m,rt<<1);
update(c,m+1,r,rt<<1|1);
}
PushUp(rt);
}
__int64 query(int l,int r,int rt)
{
if(l == tree[rt].l && r == tree[rt].r)
{
return tree[rt].sum;
}
PushDown(rt,tree[rt].r - tree[rt].l + 1);
int m=tree[rt].mid();
__int64 res = 0;
if(r <= m) res += query(l,r,rt<<1);
else if(l > m) res += query(l,r,rt<<1|1);
else
{
res += query(l,m,rt<<1);
res += query(m+1,r,rt<<1|1);
}
return res;
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
//按位取反
{
build(1,n,1);
while(m--)
{
char ch[2];
scanf("%s",ch);
int a,b,c;
if(ch[0] == 'Q')
{
scanf("%d %d", &a,&b);
printf("%I64d\n",query(a,b,1));
}
else
{
scanf("%d %d %d",&a,&b,&c);
update(c,a,b,1);
}
}
}
return 0;
}
-----------------------------
再给你们点福利,两组测试数据奉上:
10 4
1 5 8 2 5 9 3 5 8 9
Q 3 6
C 4 6 1
Q 5 8
Q 4 5
24
24
9
==================
29 5
2 5 2 3 6 7 3 5 8 3 7 5 4 1 2 6 8 3 6 3 2 5 7 9 8 6 3 1 5
Q 3 19
C 1 28 5
Q 3 6
C 3 6 8
Q 5 10
79
38
78
—————————————————————————————就这样的,然后不懂的可以留言,还有一道线段树poj3667也很适合,会在稍后奉上。
- 线段树学习心得 poj3468
- poj3468---线段树
- 线段树 poj3468
- poj3468之线段树
- 线段树--poj3468
- POJ3468 ZKW线段树
- poj3468(线段树)
- 线段树 pku poj3468
- 线段树-poj3468
- POJ3468 线段树
- poj3468(线段树)
- poj3468 线段树
- poj3468 zkw线段树
- poj3468-线段树详解
- 线段树模板 poj3468
- POJ3468 线段树模板
- POJ3468 线段树||伸展树
- poj3468(线段树区间求和)
- 连接池
- JAVA009-对象的行为
- Android studio 设置主题字体以及第一次启动虚拟机HAX kernel module is not installed问题
- 网络编程学习小结
- C++ 第十三周 多态性 项目1【项目1-分数类中的运算符重载】
- 线段树学习心得 poj3468
- 第十三章 限制 第十四章 参考
- 机器学习中的数据集合
- Spring+SpringMVC+mybatis入门(环境搭建+crud)
- 剑指offer(三十)之扑克牌顺子
- 快速排序里的学问:随机化快排
- 从零开始学回溯算法
- DHCP源码分析-报文解析和封装
- HDOJ3565 Bi-peak Number