线段树入门&lazy思想
线段树将区间分成若干个子区间,子区间又继续分,直到区间为一个点(区间左值等于右值)
对于父区间[a,b],其子区间为[a,(a+b)/2]和[(a+b)/2+1,b]
用于求区间的值,如区间最值、区间的和等。
代码实现中,约定结点下标从1开始,所以某结点下标为x,那么左儿子下标为2x,右儿子下标为2x+1,父结点下标为x/2。
符号 | 等价 | 意义 |
---|---|---|
rt<<1 | rt*2 | 左子树的编号 |
rt<<1|1 | rt*2+1 | 右子树的编号 |
(l+r)>>1 | (l+r)/2 | 区间长度的一半 |
常用宏定义
#define Mid ((l+r)>>1) //注意括号
#define lson rt<<1,l,Mid //左结点
#define rson rt<<1|1,Mid+1,r //右结点
建树
建树丛根结点开始,递归建立左右子树,直到叶子结点,然后反向赋值,父结点的值 = F(左结点的值,右结点的值),这个F是依据题意变的,如果是区间最大则为max()
void build(int rt,int l,int r) //建编号为rt的区间为[l,r]的树,主函数传进来的固定是(1,1,n)
{
if(l==r){ //叶子结点赋初值,注意下标,Max的是编号,val原数组的是l,看图可以理解
Max[rt] = val[l];
}else{ //建左右子树
build(lson);
build(rson);
Max[rt] = max( Max[rt<<1], Max[rt<<1|1]); //父结点Max值为Max(左子结点,右子结点)
}
}
查询
查询为区间查询(只是查询某个点的话不需要线段树),即在区间里查询某个特性值,每次查询都是从跟结点开始往下,根据查询区间和当前区间的区间位置判断是要去左右子区间查询,还是直接返回。如果被查询区间是查询区间的子区间则直接返回子区间的值,如在[1,6]里查询[1,12]就返回[1,6]的值,不再往下查询。
void query(int rt,int l,int r,int L,int R) //在[l,r]里查询[L,R]的值,[L,R]一直不变,[l,r]变
{
if(L <= l && r <= R){
ans1 = max(ans1,Max[rt]);
ans2 = min(ans2,Min[rt]);
}else{
if( L <= Mid) //查询区间在当前区间的左半区间有内容,如在[1,6]里查询[2,3]
query(lson,L,R);
if( R > Mid) //同理去右子区间,注意不能有else,因为有横跨左右的情况,如[1,6]里查询[2,5]
query(rson,L,R);
}
}
更新
更新分为单点更新和区间更新,区间更新等会在下面讲述,而单点更新跟普通区间查询差不多
void update(int rt,int l,int r,int pos,int num)
{
if(l == r && r == pos){ //到对应的叶结点
Max[rt] = num;
}else{
if( pos <= Mid)
update(lson,pos,num);
if( pos > Mid) //或者直接else,点不可能同时在两个区间里
update(rson,pos,num);
Max[rt] = max( Max[rt<<1], Max[rt<<1|1]);
}
}
数组大小要为原数据范围的4倍,证明点这里
题目
POJ 3264 Balanced Lineup 求区间的最大值-最小值
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define RE freopen("1.in","r",stdin);
#define bug(x) cout<<#x<<":"<<(x)<<endl;
#define Mid ((l+r)>>1) //括号!
#define lson rt<<1,l,Mid
#define rson rt<<1|1,Mid+1,r
const int maxn= 50005;
const int inf=0x3f3f3f3f;
int Max[maxn<<2],Min[maxn<<2];
int ans1,ans2;
void build(int rt,int l,int r)
{
if(l==r){
scanf("%d",&Max[rt]);
Min[rt] = Max[rt];
}else{
build(lson);
build(rson);
Max[rt] = max( Max[rt<<1], Max[rt<<1|1]);
Min[rt] = min( Min[rt<<1], Min[rt<<1|1]);
}
}
void query(int rt,int l,int r,int L,int R)
{
if(L <= l && r <= R){
ans1 = max(ans1,Max[rt]);
ans2 = min(ans2,Min[rt]);
}else{
if( L <= Mid)
query(lson,L,R);
if( R > Mid)
query(rson,L,R);
}
}
int main()
{
int n,m,L,R;
while(scanf("%d%d",&n,&m)!=EOF){
build(1,1,n);
while(m--){
ans1 = -inf,ans2 = inf;
scanf("%d%d",&L,&R);
query(1,1,n,L,R);
printf("%d\n", ans1-ans2);
}
}
return 0;
}
HDU 1754 I Hate It单点更新,区间查询最大
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define RE freopen("1.in","r",stdin);
#define bug(x) cout<<#x<<":"<<(x)<<endl;
#define Mid ((l+r)>>1)
#define lson rt<<1,l,Mid
#define rson rt<<1|1,Mid+1,r
const int maxn= 200000+5;
const int inf=0x3f3f3f3f;
int Max[maxn<<2];
int ans1,ans2;
void build(int rt,int l,int r)
{
if(l==r){
scanf("%d",&Max[rt]);
}else{
build(lson);
build(rson);
Max[rt] = max( Max[rt<<1], Max[rt<<1|1]);
}
}
void update(int rt,int l,int r,int pos,int num)
{
if(l == r && r == pos){
Max[rt] = num;
}else{
if( pos <= Mid)
update(lson,pos,num);
if( pos > Mid)
update(rson,pos,num);
Max[rt] = max( Max[rt<<1], Max[rt<<1|1]);
}
}
int query(int rt,int l,int r,int L,int R)
{
if(L <= l && r <= R){
return Max[rt];
}else{
int tmp = -1;
if( L <= Mid)
tmp = max(tmp,query(lson,L,R));
if( R > Mid)
tmp = max(tmp,query(rson,L,R));
return tmp;
}
}
int main()
{
// RE
int n,m,L,R;
char op;
while(scanf("%d%d",&n,&m)!=EOF){
build(1,1,n);
getchar();
while(m--){
scanf("%c%d%d%*c",&op,&L,&R);
if(op=='Q')
printf("%d\n",query(1,1,n,L,R));
else
update(1,1,n,L,R);
}
}
return 0;
}
单点更新,查询区间和【此题树状数组解法转见→】
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define RE freopen("1.in","r",stdin);
#define bug(x) cout<<#x<<":"<<(x)<<endl;
#define Mid ((l+r)>>1)
#define lson rt<<1,l,Mid
#define rson rt<<1|1,Mid+1,r
const int maxn= 50000+5;
const int inf=0x3f3f3f3f;
int sum[maxn<<2];
int ans1,ans2;
void build(int rt,int l,int r)
{
if(l==r){
scanf("%d",&sum[rt]);
}else{
build(lson);
build(rson);
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
}
void update(int rt,int l,int r,int pos,int num)
{
if(l == r && r == pos){
sum[rt] += num;
}else{
if( pos <= Mid)
update(lson,pos,num);
else
update(rson,pos,num);
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
}
int query(int rt,int l,int r,int L,int R)
{
if(L <= l && r <= R){
return sum[rt];
}else{
int tmp = 0;
if( L <= Mid)
tmp += query(lson,L,R);
if( R > Mid)
tmp += query(rson,L,R);
return tmp;
}
}
int main()
{
// RE
int n,m,L,R,t;
char op[10];
scanf("%d",&t);
for(int cas=1;cas<=t;cas++){
scanf("%d",&n);
build(1,1,n);
printf("Case %d:\n",cas);
while(scanf("%s",op),op[0]!='E'){
scanf("%d%d",&L,&R);
if(op[0]=='Q')
printf("%d\n", query(1,1,n,L,R));
else if(op[0]=='A')
update(1,1,n,L,R);
else
update(1,1,n,L,-R);
}
}
return 0;
}
区间成段更新
Lazy:正常来说,区间改值,当更改某个区间的值的时候,子区间也该跟着更改,这样容易TLE。
Lazy思想就是更新到某个区间的时候,就先给这个区间打上标记,标记内容是需要更新的值,并把子区间的值改为子区间对应的值,清除该区间的lazy标记;然后return,不去更新子区间。当下一次更新或查询等需要访问该区间的子区间的时候再把该区间的lazy和其他信息送回子区间。
举个简单粗暴的例子:
对应下面的那个图,假如目的是求和,现在要给[1,6] 的值都加2,那么我们从[1,12]->[1,6],然后[1,6]的sum值加上区间长度[ (6-1+1)*2 ],再把[1,6]的add[i]设置为2,就不再往下更新了【这里极大提高效率】。下一次更新/查询[1,6]的子区间时,我们将[1,6]原存的add值下传给[1,6]的两个直接子区间,再往下更新。假设在这种情况下,我们再更新[1,6]加3,则[1,6]的add值为2+3=5,然后我们查询[1,3],则从上往下经过[1,6]时把[1,6]的add值给了子区间[1,3]和[4,6],同时把sum[子区间]跟着子区间长度和add[父结点]改动,清除add[父节点]。【如果是查询间接子区间,则连续传递add值,也就是连续pushDown】
详细例子:假设update()是区间改值,query()是求和,所有叶子区间的和都为1,则[7,8]和[7,9]在build()的时候就附上了值(图中绿色字体)。假设此时我们更新[7,9]的值,改为2,则线段树从[1,12]->[7,12]->[7,9],然后把[7,9]打上值为2的标记,求和(求和直接用区间长度*此时更新的值),然后不去更新[7,8]和[9,9]了,他们值仍然是2和1,lazy值为0。
然后我们查询[7,8],当遍历经过[7,9]时
if(add[i])
pushDown(i);
成立,把[7,9]的lazy标记2传给子区间[7,8]和[9,9],分别求这2个子区间的和,把[7,9]的lazy标记去掉,然后继续遍历,到[7,8]的时候直接返回答案。
HDU 1698 Just a Hook 区间改值,求和
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define RE freopen("1.in","r",stdin);
#define WE freopen("1.out","w",stdout);
#define MOD 10009
#define bug(x) cout<<#x<<":"<<(x)<<endl;
#define Mid ((l+r)>>1)
#define lson rt<<1,l,Mid
#define rson rt<<1|1,Mid+1,r
const int maxn = 100010;
int sum[maxn<<2],add[maxn<<2];
void build(int rt,int l,int r)
{
add[rt] = 0;
if(l == r){
sum[rt] = 1;
}else{
build(lson);
build(rson);
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
}
void pushDown(int rt,int len)
{
add[rt<<1] = add[rt<<1|1] = add[rt];
sum[rt<<1] = (len-(len>>1))*add[rt];
sum[rt<<1|1] = (len>>1)*add[rt];
add[rt] = 0;
}
void update(int rt,int l,int r,int L,int R,int z)
{
if(L <= l && r <= R){
add[rt] = z;
sum[rt] = (r-l+1)*z;
}else{
if(add[rt])
pushDown(rt,r-l+1);
if(L <= Mid)
update(lson,L,R,z);
if(R > Mid)
update(rson,L,R,z);
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
}
int main()
{
// RE;
int t,n,q,x,y,z;
int cnt=1;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&q);
build(1,1,n);
while(q--){
scanf("%d%d%d",&x,&y,&z);
update(1,1,n,x,y,z);
}
printf("Case %d: The total value of the hook is %d.\n", cnt++,sum[1]);
}
return 0;
}
线段树入门&lazy思想的更多相关文章
- POJ 2808 校门外的树(线段树入门)
题目描述 某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米.我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置:数轴上的每个整数点,即0,1,2,……,L,都种 ...
- Codeforces 588E. A Simple Task (线段树+计数排序思想)
题目链接:http://codeforces.com/contest/558/problem/E 题意:有一串字符串,有两个操作:1操作是将l到r的字符串升序排序,0操作是降序排序. 题解:建立26棵 ...
- 线段树初步&&lazy标记
线段树 一.概述: 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a, ...
- POJ 2155 Matrix (二维线段树入门,成段更新,单点查询 / 二维树状数组,区间更新,单点查询)
题意: 有一个n*n的矩阵,初始化全部为0.有2中操作: 1.给一个子矩阵,将这个子矩阵里面所有的0变成1,1变成0:2.询问某点的值 方法一:二维线段树 参考链接: http://blog.csdn ...
- POJ 3237 Tree (树链剖分 路径剖分 线段树的lazy标记)
题目链接:http://poj.org/problem?id=3237 一棵有边权的树,有3种操作. 树链剖分+线段树lazy标记.lazy为0表示没更新区间或者区间更新了2的倍数次,1表示为更新,每 ...
- ZKW线段树入门
Part 1 来说说它的构造 线段树的堆式储存 我们来转成二进制看看 小学生问题:找规律 规律是很显然的 一个节点的父节点是这个数左移1,这个位运算就是低位舍弃,所有数字左移一位 一个节点的子节点是这 ...
- HDU1754 I hate it_线段树(入门级别)
I Hate It Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total S ...
- 线段树的lazy(poj3468)
A Simple Problem with Integers Time Limit: 5000MS Memory Limit: 131072K Total Submissions: 73163 ...
- 浅谈算法——线段树之Lazy标记
一.前言 前面我们已经知道线段树能够进行单点修改和区间查询操作(基本线段树).那么如果需要修改的是一个区间该怎么办呢?如果是暴力修改到叶子节点,复杂度即为\(O(nlog n)\),显然是十分不优秀的 ...
随机推荐
- Ubuntu下Gradle环境配置
sudo gedit ~/.profile sudo source ~/.profile env # for java export JAVA_HOME=/home/cmm/jdk export CL ...
- [转] ReactJS之JSX语法
JSX 语法的本质目的是为了使用基于 xml 的方式表达组件的嵌套,保持和 HTML 一致的结构,语法上除了在描述组件上比较特别以外,其它和普通的 Javascript 没有区别. 并且最终所有的 J ...
- LeetCode-450 二叉搜索树删除一个节点
二叉搜索树 建树 删除节点,三种情况,递归处理.左右子树都存在,两种方法,一种找到左子树最大节点,赋值后递归删除.找右子树最小同理 class Solution { public: TreeNode* ...
- ssm又乱码
以前用mybatis的时候是这样的 mysql.connection.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characte ...
- 【loj6142】「2017 山东三轮集训 Day6」A 结论题+Lucas定理
题解: 当奇数 发现答案就是C(n,1)^2+C(n,3)^2+...C(n,n)^2 倒序相加,发现就是C(2n,n) 所以答案就是C(2n,n)/2 当偶数 好像并不会证 打表出来可以得到 2.当 ...
- MySQl 查询性能优化相关
0. 1.参考 提升网站访问速度的 SQL 查询优化技巧 缓存一切数据,读取内存而不是硬盘IO 如果你的服务器默认情况下没有使用MySQL查询缓存,那么你应该开启缓存.开启缓存意味着MySQL 会把所 ...
- VM VirtualBox – Cannot register the hard disk
第一打开VirtualBox 文件夹,在地址栏输入cmd 第二, 仔细读下面 VBoxManage.exe internalcommands sethduuid "F:\Virtual ...
- 2018牛客网暑假ACM多校训练赛(第四场)B Interval Revisited 动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/NowCoder-2018-Summer-Round4-B.html 题目传送门 - https://www.no ...
- Echarts官网展示
1.参考实例 http://echarts.baidu.com/examples/ 点击去的效果: 2.配置项手册 http://echarts.baidu.com/option.html#title ...
- 多个SDK控制管理
需求:制作一个公共组件,可以实现多个SDK想用哪个用哪个,集中管理 组织方式: 架构形式 注意点: 1.sdk必须通过maven库来compile,因为jar会打到aar中:所以library和主mo ...