(区间线段树)lintcode困难1074 · 范围模块

题目

描述
范围模块是跟踪数字范围的模块。 您的任务是以有效的方式设计和实现以下接口。

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;
}










总结

这道题的难点在于懒标记的使用,可以说是一种非常巧妙的算法节省了很多不必要的更新,也算是线段树模板的一种变形,一定要做到深刻的理解,我也是看了几遍代码然后加上画图才理解了懒标记的核心

May you succeed

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

White boy&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值