本来以为把伸展树看懂了,结果分析题目的时候,且不说自己做了,别人的代码都读不懂!看到伸展树操作中也有懒操作的身影,就重新回到基础,学习了一下线段树的懒操作问题!
半天时间就这样过去了!不过还好的是勉强AC了这道题!
这道题是一道线段树很基础的题:更新区间,查询区间!题目大意是给定一个区间,0表示空闲,1表示区间单元被占据。问整个区间最大的连续子区间有多大?
记得以前做过类似的题目。线段树结点需要以下的信息:
- struct Tree{
- int ll,rr,mid;
- int max,cl,cr;//max表示结点管理的区间最大的连续子区间有多大,cl表示区间的最左边有多少连续的区间,cr表示区间的右边有多少连续的子区间
- int occ;//occ==0表示空,occ=1表示全部入住,occ=-1表示有空有住
- };
由于查询的是整个区间的最大值,最后查询的时候只需要输出根结点的max即tree[1].max就可以了。
为了提高线段树的效率,以相同的方式更新某区间时,(比如对整个区间同时add一个数)并不去真正更新其子区间,而是以某种方式把这个操作记录下来,等下一次访问到该区间并需要访问其子区间时,在访问的同时把子区间的值进行更改!这种思想就称为懒操作。
像POJ的那道题就是典型的懒操作!
思路很简单就不细说了,看代码可能更容易理解:
- /*
- * IntervalTree.cpp
- *
- * Created on: 2012-10-12
- * Author: Administrator
- */
- #include<stdio.h>
- #define max(a,b) (a)>(b)?(a):(b)
- #define M 20000
- struct Tree{
- int ll,rr,mid;
- int max,cl,cr;//max表示结点管理的区间最大的连续子区间有多大,cl表示区间的最左边有多少连续的区间,cr表示区间的右边有多少连续的子区间
- int occ;//occ==0表示空,occ=1表示全部入住,occ=-1表示有空有住
- };
- Tree tree[4*M];
- int n,p;
- void build(int id,int ll,int rr){
- tree[id].ll=ll,tree[id].rr=rr,tree[id].mid=(ll+rr)>>1;
- //刚开始建树,都是空的,所以可以这样写
- tree[id].max=tree[id].cl=tree[id].cr=rr-ll+1;
- tree[id].occ=0;
- if(ll==rr)return;
- build(id*2,ll,tree[id].mid);
- build(id*2+1,tree[id].mid+1,rr);
- }
- void push_down(int id,bool sign){//sign=true表示入住,sign=false表示退房
- tree[id].occ=-1;//表示有空有住
- tree[2*id].occ=tree[2*id+1].occ=sign;//表示全空或者全住
- if(sign){
- tree[2*id].cl=tree[2*id+1].cl=tree[2*id].cr=tree[2*id+1].cr=0;
- tree[2*id].max=tree[2*id+1].max=0;
- }else{
- int len=tree[2*id].rr-tree[2*id].ll+1;
- tree[2*id].cl=tree[2*id].cr=len;
- tree[2*id].max=len;
- len=tree[2*id+1].cr=tree[2*id+1].rr-tree[2*id+1].ll+1;
- tree[2*id+1].cl=len;
- tree[2*id+1].max=len;
- }
- }
- //更新区间
- void update(int id ,int ll,int rr,bool sign){//sign=true表示入住,sign=false表示退房
- if(tree[id].ll==ll&&tree[id].rr==rr){//找到区间
- tree[id].occ=sign;
- int len=tree[id].rr-tree[id].ll+1;
- sign==1?len=0:len;
- tree[id].cl=tree[id].cr=len;
- tree[id].max=len;
- return;
- }
- if(tree[id].occ!=-1)push_down(id,tree[id].occ);//执行到这行代码意味着 tree[id]的子区间要更改了,所以需要执行一次push_down
- if(rr<=tree[id].mid){
- update(id*2,ll,rr,sign);
- }else if(ll>tree[id].mid){
- update(id*2+1,ll,rr,sign);
- }else{
- update(id*2,ll,tree[id].mid,sign),
- update(id*2+1,tree[id].mid+1,rr,sign);
- }
- //需要修改的就只有3个值:max,cl,cr 分别代表最长连续个数为多少,最左边有多少个空闲,最右边有多少个空闲
- if(tree[id].occ==-1){//表示有空有住
- if(tree[2*id].occ==0){
- tree[id].cl=tree[id*2].cl+tree[2*id+1].cl;
- }else{
- tree[id].cl=tree[2*id].cl;
- }
- if(tree[2*id+1].occ==0){
- tree[id].cr=tree[id*2].cr+tree[2*id+1].cr;
- }else{
- tree[id].cr=tree[2*id+1].cr;
- }
- //求tree[id].max
- int len=tree[2*id].cr+tree[2*id+1].cl;
- tree[id].max=max(len,max(tree[2*id].max,tree[2*id+1].max));
- }else{//表示全空或者全住
- int len;
- sign==0?len=0:len=tree[id].rr-tree[id].ll+1;
- tree[id].max=tree[id].cl=tree[id].cr=len;
- }
- if(tree[id*2].occ==tree[id*2+1].occ)
- tree[id].occ=tree[id*2].occ;
- }
- int main(){
- int i;
- scanf("%d %d",&n,&p);
- int sign;
- int ll,rr;
- build(1,1,n);
- for(i=0;i<p;i++){
- scanf("%d",&sign);
- if(sign==3){
- //output
- printf("%d\n",tree[1].max);
- }else{
- scanf("%d %d",&ll,&rr);
- rr=ll+rr-1;
- sign!=1? sign=0:sign=1;
- update(1,ll,rr,sign);
- }
- }
- return 0;
- }
相关的线段树懒操作的题目有:hdu2871,poj3468,poj3667
|