小白初识线段树(线段树原理+专题练习)
来源:互联网 发布:m2m 数据采集 编辑:程序博客网 时间:2024/06/17 05:59
希望再次遇到线段树的题目时可以AC!
线段树其实就是个二叉树。我们一般用数组来进行模拟(如果用结构体+指针也是可以的)。注意,这个二叉树不一定是个完全二叉树。
问题1.如果当前有个数组是的下表是0-(n-1),即有n个元素,那么需要多那么大数组来存放线段树呢。
其实,他的实际有效的空间是2*n-1个,但是我们如果用数组来进行模拟,父亲节点的两个儿子节点我们是如何表示的?
我们是father*2+1,father*2+2来表示的吧?这样,虽然你确确实实是用了2*n-1个空间,但是这些空间反应在数组内是存在间隙的。
我查阅了一下网上的资料,发现,如果数组中有n个元素,那么用来模拟线段树的数组开到4*n是足够的.
问题2.如何建立一个线段树(节点表示区间最小值)。
大体思路:就如同二分法,不断地对折区间,极限状态时区间的左边等于右边,这个时候赋值。代码如下
大体思路:假设我们的查询区间是ql,qr,当前区间是l,r
只有当前区间是查询区间的子集的时候,我们才能返回当前的值。如果这两个区间没有交集,那么对于最小值来说就直接返回正无穷(为了不干扰其他值的向上的传递)。
若有交集,那么就继续查找儿子节点。代码如下。
问题4。单点更新?
大体思路:idx为我们要更新元素的下表,newval是我们要更新为什么值。二分二分二分............之后分到一个点之后就是了
代码:
最精华的部分-------延迟标记!其实按照我的理解就是什么时候用,什么时候更新,用不着根本不用跟新!
这个意思就跟并查集的合并节点是一样的。我们令pre[a]=b;就真让以a为头节点的集合全都并到b上去了吗?当然不是,只是头节点a并上去了而已。
这时候你再去输出一下以a为头结点的元素的头结点试试?还是a,并没有因为a并过去了而a麾下所有的节点都并过去了。
但是当你需要找到以a为头结点元素的头结点的时候又能更新到b上去了,这个其实就是延迟标记的原理。
想一下,确实是延迟,但是不等于没做,只是在最需要的时候做了而已,不需要的时候不要有多余的动作。
怎么去实现?
这时候我们引进一个标记元素,用来标记当前的操作。然后我们还需要写一个函数,用来传递关系。这里看不懂没关系,只要红字能看懂了代码也能看懂,稍加思考就可以学会。学会之后就会做题了。
引入标记数组key[],操作区间cl,cr,操作(增加)值val。找区间的时候就跟区间查询一样一样的。找到了之后直接更新就行,但是要在这个点进行标记,说明这个点还是要
向下更新的,只不过现在不需要!
问题1.如果当前有个数组是的下表是0-(n-1),即有n个元素,那么需要多那么大数组来存放线段树呢。
其实,他的实际有效的空间是2*n-1个,但是我们如果用数组来进行模拟,父亲节点的两个儿子节点我们是如何表示的?
我们是father*2+1,father*2+2来表示的吧?这样,虽然你确确实实是用了2*n-1个空间,但是这些空间反应在数组内是存在间隙的。
我查阅了一下网上的资料,发现,如果数组中有n个元素,那么用来模拟线段树的数组开到4*n是足够的.
问题2.如何建立一个线段树(节点表示区间最小值)。
大体思路:就如同二分法,不断地对折区间,极限状态时区间的左边等于右边,这个时候赋值。代码如下
const int M=1000;int save[M];int tree[4*M];void creat_tree(int l,int r,int now){ if(l==r)//递归结束点 { tree[now]=save[l];//赋值 return ; } else { int mid=(r+l)/2; creat_tree(l,mid,now*2+1);//建立左儿子 creat_tree(mid+1,r,now*2+2);//建立右儿子 tree[now]=min(tree[now*2+1],tree[now*2+2]);//回溯的时候把最小值求出来 }}问题3.如何查询?
大体思路:假设我们的查询区间是ql,qr,当前区间是l,r
只有当前区间是查询区间的子集的时候,我们才能返回当前的值。如果这两个区间没有交集,那么对于最小值来说就直接返回正无穷(为了不干扰其他值的向上的传递)。
若有交集,那么就继续查找儿子节点。代码如下。
int query(int l,int r,int now,int ql,int qr){ if(ql<=l&&qr>=r)//当前区间是查询区间的子集 { return tree[now]; } if(ql>r||qr<l)//完全不相交 { return 0x3f3f3f3f;//INF } int mid=(l+r)/2; return min(query(l,mid,now*2+1,ql,qr),query(mid+1,r,now*2+2,ql,qr));//返回查找到的最小值}
问题4。单点更新?
大体思路:idx为我们要更新元素的下表,newval是我们要更新为什么值。二分二分二分............之后分到一个点之后就是了
代码:
void update(int l,int r,int now,int idx,int newval)//把下表为ind的元素更新为newval{ if(l==r)//逼近到一个点之后开始更新 { tree[now]=newval; return ; } int mid=(l+r)/2; if(mid>idx)//又儿子更新,左儿子更新 { update(mid+1,r,now*2+2,idx,newval); } else { update(l,mid,now*2+1,idx,newval); } tree[now]=min(tree[now*2+1],tree[now*2+2]);}问题5:我想一次更新一个区间的值怎么办?
最精华的部分-------延迟标记!其实按照我的理解就是什么时候用,什么时候更新,用不着根本不用跟新!
这个意思就跟并查集的合并节点是一样的。我们令pre[a]=b;就真让以a为头节点的集合全都并到b上去了吗?当然不是,只是头节点a并上去了而已。
这时候你再去输出一下以a为头结点的元素的头结点试试?还是a,并没有因为a并过去了而a麾下所有的节点都并过去了。
但是当你需要找到以a为头结点元素的头结点的时候又能更新到b上去了,这个其实就是延迟标记的原理。
想一下,确实是延迟,但是不等于没做,只是在最需要的时候做了而已,不需要的时候不要有多余的动作。
怎么去实现?
这时候我们引进一个标记元素,用来标记当前的操作。然后我们还需要写一个函数,用来传递关系。这里看不懂没关系,只要红字能看懂了代码也能看懂,稍加思考就可以学会。学会之后就会做题了。
引入标记数组key[],操作区间cl,cr,操作(增加)值val。找区间的时候就跟区间查询一样一样的。找到了之后直接更新就行,但是要在这个点进行标记,说明这个点还是要
向下更新的,只不过现在不需要!
const int M=1000;int save[M];int tree[4*M];int key[4*M];void pushdown(int now){ if(now) { key[now*2+1]+=key[now]; key[now*2+1]+=key[now];//向下传递 tree[now*2+1]+=key[now];//向下赋值 tree[now*2+2]+=key[now]; key[now]=0;//传递结束后自己的使命完成,恢复为0 }}void creat_tree(int l,int r,int now){ key[now]=0;//创建的时候就清空他 if(l==r)//递归结束点 { tree[now]=save[l];//赋值 return ; } else { int mid=(r+l)/2; creat_tree(l,mid,now*2+1);//建立左儿子 creat_tree(mid+1,r,now*2+2);//建立右儿子 tree[now]=min(tree[now*2+1],tree[now*2+2]);//回溯的时候把最小值求出来 }}int query(int l,int r,int now,int ql,int qr){ if(ql<=l&&qr>=r)//当前区间是查询区间的子集 { return tree[now]; } if(ql>r&&qr<l)//完全不相交 { return 0x3f3f3f3f;//INF } pushdown(now); int mid=(l+r)/2; return min(query(l,mid,now*2+1,ql,qr),query(mid+1,r,now*1+2,ql,qr));//返回查找到的最小值}void update(int l,int r,int now,int idx,int newval)//把下表为ind的元素更新为newval{ if(l==r)//逼近到一个点之后开始更新 { tree[now]=newval; return ; } int mid=(l+r)/2; if(mid>idx)//又儿子更新,左儿子更新 { update(mid+1,r,now*2+2,idx,newval); } else { update(l,mid,now*2+1,idx,newval); } tree[now]=min(tree[now*2+1],tree[now*2+2]);}void Xupdate(int l,int r,int now,int cl,int cr,int cval){ if(cl<=l&&cr>=r) { tree[now]+=cval;//更新该区间节点的值 key[now]+=cval;//延迟标记的累计,因为可能连续的执行好几次这样的标记,所以要累加起来 return ; } if(cl>r||cr<l) { return ; } pushdown(now);//顺风车,不搭白不搭?错了错了!不是这样!!!是必须要!因为你必须保证每次回溯的值都是对的,如果没有这一步,那么回溯的值很可能就是个错的 int mid=(r+l); Xupdate(l,mid,now*2+1,cl,cr,cval); Xupdate(mid+1,r,now*2+2,cl,cr,cval); tree[now]=min(tree[now*2+1],tree[now*2+2]);}由于小白,会有一些错误,望指正,之后会更新kuangbin的线段树专题QQQQQQAQQQQQQQ
阅读全文
0 0
- 小白初识线段树(线段树原理+专题练习)
- 初识线段树(线段树总结)
- codevs1080线段树练习(线段树)
- 线段树专题介绍
- 线段树专题
- 【专题属性】线段树
- 【专题】线段树(完整版)
- 线段树专题
- 线段树专题
- poj线段树专题
- 【专题】线段树
- 线段树专题
- 线段树专题
- 线段树专题
- 线段树专题
- 线段树专题
- 线段树专题训练
- 线段树专题
- 图像的平移和缩放
- normalize、splitText
- 关于RecyclerView中含有CheckBox,Button等控件失去焦点的解决办法
- pip install --upgrade pip 9.0.1 error
- Bot Framework使用Direct Line实现第三方渠道调用Bot服务
- 小白初识线段树(线段树原理+专题练习)
- 从AIDL来认识Binder
- iOS AVCapture前置摄像头不显示镜像翻转
- Python库的安装方法
- 使用可编辑JComboBox的时遇到的一个坑,编辑后如果JCombobox的焦点还在,则获取不到当前的编辑值。
- ZOJ--1010:Area(线段判交问题)
- 西财2018届电商研究生招生简介
- iOS
- poj3620 DFS