题目
描述
范围模块是跟踪数字范围的模块。 您的任务是以有效的方式设计和实现以下接口。
addRange(int left,int right): 添加左闭右开[left,right)的区间,跟踪区间中的每个实数。 如果添加的区间里与已经跟踪的实数部分重合,那么就把区间内没有跟踪的实数也加进去。
queryRange(int left,int right): 当且仅当当前[left,right)中的每个实数都被跟踪时,返回true。
removeRange(int left,int right): 停止跟踪[left,right)区间内当前已经跟踪的每个实数。
提示
一个左闭右开的区间 [left, right) 包含了 left <= x < right范围内所有的实数.
函数 addRange, queryRange, removeRange中参数的取值范围为0 < left < right < 10^9.
测试样例中调用addRange的次数最多为 1000.
测试样例中调用queryRange 的次数最多为5000.
测试样例中调用removeRange的次数最多为 1000.
样例
样例1
输入:
addRange(10,20)
removeRange(14,16)
queryRange(10,14)
queryRange(13,15)
queryRange(16,17)
输出: [true,false,true]
说明:
[10, 14)里的所有数字有已被跟踪
一些数字,例如:14, 14.03, 14.17 [13, 15)并没有被跟踪
尽管有remove的操作,区间[16, 17)中的16仍被跟踪
样例2
输入:
addRange(1,2)
queryRange(2,3)
addRange(11,20)
queryRange(15,20)
输出: [false,true]
分析
一道线段树的模板题,与之前不同的是,这次修改的是区间,之前修改的是单个节点,我们只需要改变当前区间中的所有子区间即可,然后看一下数据范围1e9,我们首先的初始化就需要至少1e9次运算,所以按照之前的方法一定是过不了的,这里我们运用到了懒标记
懒标记大概就是,找到当前的区间进行标记,但是并不标记它的子区间,等到下次修改或者查找的时候我们顺便做一下它的子区间标记
代码部分
1.线段树节点
特有属性:
一个布尔类型的变量代表当前区间是否被跟踪
一个int类型变量代表当前节点的孩子节点是否需要被更新(懒标记)
//线段树节点
class SegmentTreeNode
{
public:
int start,end;
bool tracked; //是否被追踪
int childupdate; //子节点是否需要更新
SegmentTreeNode* left;
SegmentTreeNode* right;
SegmentTreeNode(int s,int e,int t):start(s),end(e),tracked(t)
{
left=NULL;
right=NULL;
}
};
2.线段树的初始化
这里用到懒标记,只标记当前整个的区间为未被跟踪,并不对子区间进行标记
//构造函数
SegmentTree()
{
//懒标记 初始化默认每个区间都没有被追踪
root=new SegmentTreeNode(0,int(1e9),false);
}
3.添加区间
这个相当于修改部分,但是修改的是区间,我们进行递归深入,找出左右区间与目标区间重合的部分进行标记,然后将它的childupdate标记成1,表示它的孩子需要更新但是我们现在不去做更新
出口我们判断当前节点的左右节点与目标左右节点重合时,我们就进行标记然后返回
回溯部分,我们应该更新当前的节点,如果它的左右区间都被标记成了跟踪,那它也应该被标记成跟踪
//添加区间
void addrange(SegmentTreeNode* _root,int start,int end)
{
//出口
// cout<<start<<" "<<end<<endl;
if(_root->start==start&&_root->end==end)
{
_root->tracked=true;
_root->childupdate=1;
return ;
}
updatechild(_root);
//现在能做的事情
int mid=(_root->start+_root->end)/2;
if(mid>=start)
{
addrange(_root->left,start,min(end,mid));
}
if(mid+1<=end)
{
addrange(_root->right,max(mid+1,start),end);
}
//当前区间的子区间都被追踪时,当前区间自然也被跟踪
_root->tracked= _root->left->tracked & _root->right->tracked;
}
4.更新孩子节点
这里我们遍历到当前节点时顺便做的事情,判断当前节点的左右子节点是否被初始化,如果没有,我们对它进行初始化的操作。
如果已经被初始化,我们判断当前节点的左右子节点是否需要更新,如果需要更新的话我们就更新左右子节点的标记,并且改变左右子节点的childupdate
这里不进行递归的深入,旨在顺便判断当前节点,然后更新当前节点的左右子节点即可(思想有点像并查集中的路径压缩)
//更新子区间的标记(懒标记 下次在更新到当前区间时在进行更新子区间)
void updatechild(SegmentTreeNode* _root)
{
int mid=(_root->start+_root->end)/2;
if(_root->left==NULL&&_root->right==NULL)
{
_root->left=new SegmentTreeNode(_root->start,mid,_root->tracked);
_root->right=new SegmentTreeNode(mid+1,_root->end,_root->tracked);
}
else if(_root->childupdate!=0)
{
//更新子区间是否被跟踪
_root->left->tracked= _root->childupdate==1?true:false;
_root->right->tracked= _root->childupdate==1?true:false;
//标记子区间的子区间是否需要更新
_root->left->childupdate=_root->childupdate;
_root->right->childupdate=_root->childupdate;
}
//更新完毕取消标记
_root->childupdate=0;
}
5.删除区间
这里跟添加区间很像,我们只需要把跟踪标记改编成false,childupdate改变成-1就好了,遍历到每个节点时,我们也做updatechild操作,顺便改变当前节点的子节点
void removerange(SegmentTreeNode* _root,int start,int end)
{
//出口
if(_root->start==start&&_root->end==end)
{
_root->tracked=0;
_root->childupdate=-1;
return;
}
updatechild(_root);
//现在能做的事情
int mid=(_root->start+_root->end)/2;
if(mid>=start)
{
removerange(_root->left,start,min(end,mid));
}
if(mid+1<=end)
{
removerange(_root->right,max(start,mid+1),end);
}
_root->tracked=_root->left->tracked & _root->right->tracked;
}
6.查询区间
出口部分跟修改区间的操作时一样的,当前区间与查询区间重合时返回,当前区间是否被跟踪,题目中给定当区间中的所有实数都被跟踪时返回true
所以这里我们递归的部分,要进行判断当前节点的左右子区间重合部分,如果都被跟踪那我们就返回true,否则返回false
//查询区间
bool queryrange(SegmentTreeNode* _root,int start,int end)
{
//出口
if(_root->start==start&&_root->end==end)
{
return _root->tracked;
}
updatechild(_root);
//现在能做的事情
int mid=(_root->start+_root->end)/2;
bool ans=true;
if(mid>=start)
{
ans= ans && queryrange(_root->left,start,min(end,mid));
}
if(mid+1<=end)
{
ans= ans && queryrange(_root->right,max(start,mid+1),end);
}
return ans;
}
完整代码
#include <bits/stdc++.h>
using namespace std;
//线段树节点
class SegmentTreeNode
{
public:
int start,end;
bool tracked; //是否被追踪
int childupdate; //子节点是否需要更新
SegmentTreeNode* left;
SegmentTreeNode* right;
SegmentTreeNode(int s,int e,int t):start(s),end(e),tracked(t)
{
left=NULL;
right=NULL;
}
};
//线段树
class SegmentTree
{
public:
SegmentTreeNode* root;
//构造函数
SegmentTree()
{
//懒标记 初始化默认每个区间都没有被追踪
root=new SegmentTreeNode(0,int(1e9),false);
}
//更新子区间的标记(懒标记 下次在更新到当前区间时在进行更新子区间)
void updatechild(SegmentTreeNode* _root)
{
int mid=(_root->start+_root->end)/2;
if(_root->left==NULL&&_root->right==NULL)
{
_root->left=new SegmentTreeNode(_root->start,mid,_root->tracked);
_root->right=new SegmentTreeNode(mid+1,_root->end,_root->tracked);
}
else if(_root->childupdate!=0)
{
//更新子区间是否被跟踪
_root->left->tracked= _root->childupdate==1?true:false;
_root->right->tracked= _root->childupdate==1?true:false;
//标记子区间的子区间是否需要更新
_root->left->childupdate=_root->childupdate;
_root->right->childupdate=_root->childupdate;
}
//更新完毕取消标记
_root->childupdate=0;
}
//添加区间
void addrange(SegmentTreeNode* _root,int start,int end)
{
//出口
// cout<<start<<" "<<end<<endl;
if(_root->start==start&&_root->end==end)
{
_root->tracked=true;
_root->childupdate=1;
return ;
}
updatechild(_root);
//现在能做的事情
int mid=(_root->start+_root->end)/2;
if(mid>=start)
{
addrange(_root->left,start,min(end,mid));
}
if(mid+1<=end)
{
addrange(_root->right,max(mid+1,start),end);
}
//当前区间的子区间都被追踪时,当前区间自然也被跟踪
_root->tracked= _root->left->tracked & _root->right->tracked;
}
//删除区间
void removerange(SegmentTreeNode* _root,int start,int end)
{
//出口
if(_root->start==start&&_root->end==end)
{
_root->tracked=0;
_root->childupdate=-1;
return;
}
updatechild(_root);
//现在能做的事情
int mid=(_root->start+_root->end)/2;
if(mid>=start)
{
removerange(_root->left,start,min(end,mid));
}
if(mid+1<=end)
{
removerange(_root->right,max(start,mid+1),end);
}
_root->tracked=_root->left->tracked & _root->right->tracked;
}
//查询区间
bool queryrange(SegmentTreeNode* _root,int start,int end)
{
//出口
if(_root->start==start&&_root->end==end)
{
return _root->tracked;
}
updatechild(_root);
//现在能做的事情
int mid=(_root->start+_root->end)/2;
bool ans=true;
if(mid>=start)
{
ans= ans && queryrange(_root->left,start,min(end,mid));
}
if(mid+1<=end)
{
ans= ans && queryrange(_root->right,max(start,mid+1),end);
}
return ans;
}
};
class Solution {
public:
SegmentTree st;
RangeModule() {
}
void addRange(int left, int right) {
st.addrange(st.root,left,right-1);
}
bool queryRange(int left, int right) {
return st.queryrange(st.root,left,right-1);
}
void removeRange(int left, int right) {
st.removerange(st.root,left,right-1);
}
};
int main (void)
{
Solution s;
s.RangeModule();
s.addRange(1,2);
cout<<s.queryRange(2,3);
s.addRange(11,20);
cout<<s.queryRange(15,20);
return 0;
}
总结
这道题的难点在于懒标记的使用,可以说是一种非常巧妙的算法节省了很多不必要的更新,也算是线段树模板的一种变形,一定要做到深刻的理解,我也是看了几遍代码然后加上画图才理解了懒标记的核心