1、关于暴力:它死了,不可能诈尸
2、那就直接数据结构吧
本题思维量和码量都比较大,很好的题目!!
从头分析......
需要区间改值,区间查询,那么就是线段树了吧。
我们的目标是:找到最左端的大于等于给定长度的连续0串的左端点(很像lower_bound)
考虑这个串在区间的存在情况:
1、整个区间都是(跨越)
2、在区间内(包含)
3、以区间左端点为起点,另一端不超过
4、以区间右端点为起点,另一端不超过
为了判断这些,我们维护一个区间长度len,与区间最长0串,左起,右起几个量比较,就能得出是否是以上几种情况。
线段树维护的东西:
struct tree { int lm,rm,sum,add,len; }t[maxn<<2]; //lmrm就是左右端点起始的空房数,sum是区间的最大空房数,add是标记(不是区间加的lazy_tag,后面会讲)len是区间长度。
就是这样。建树过程比较简单,所以的量都等于len等于r-l+1;(add除外,它等于0)
这里讲一讲标记这个东西了,它只有0,1,2三种状态。
其中,0是初始值,1表示开房操作,2表示退房操作,针对这三种不同的情况,给出以下标记下传的过程:
void spread(int p) { if(t[p].add==0)//如果0就返回不用说了 return; if(t[p].add==1) { t[lch(p)].add=t[rch(p)].add=1;//向下传递 t[lch(p)].sum=t[lch(p)].lm=t[lch(p)].rm=0;//对整区间,开房,区间最大值变成0,左右儿子的各个房间都满了,理所当然都是0 t[rch(p)].sum=t[rch(p)].lm=t[rch(p)].rm=0; } if(t[p].add==2)//退房操作 { t[lch(p)].add=t[rch(p)].add=2;//向下 t[lch(p)].sum=t[lch(p)].lm=t[lch(p)].rm=t[lch(p)].len;//类比上面1,所有都空了,所有量都是len。 t[rch(p)].sum=t[rch(p)].lm=t[rch(p)].rm=t[rch(p)].len; } t[p].add=0; }
思路清晰。。。这里比普通线段树多的一个就是,对于每个点要更新。
void update(int p)//向上更新节点信息 { if(t[lch(p)].sum==t[lch(p)].len)//如果左半个区间是空的 { t[p].lm=t[lch(p)].len+t[rch(p)].lm;//那么以左端点为起点的空房数就加上右半区间以左端点为起点的长度 } else t[p].lm=t[lch(p)].lm;//否则继承儿子的信息,也就是大区间的左端的空方不变,不需要合并更新 if(t[rch(p)].sum==t[rch(p)].len)//同上,更新右边 { t[p].rm=t[rch(p)].len+t[lch(p)].rm; } else t[p].rm=t[rch(p)].rm; t[p].sum=max(max(t[lch(p)].sum,t[rch(p)].sum),t[lch(p)].rm+t[rch(p)].lm);//更新区间最大值,三种情况,最大值在左区间,右区间,左右区间跨越 }
下面就是修改和查询了。
修改:
void change(int l,int r,int L,int R,int p,int f)//l,r是当前的左右端点,LR是给定区间,p是线段树下标,f是修改类型 { spread(p); if(L<=l&&r<=R)//整区间返回 { if(f==1) { t[p].sum=t[p].lm=t[p].rm=0;//手动更新节点信息 } else { t[p].sum=t[p].lm=t[p].rm=t[p].len; } t[p].add=f;//打标记 return; } int mid=l+r>>1; if(L<=mid)change(l,mid,L,R,lch(p),f);//线段树基本操作 if(R>mid)change(mid+1,r,L,R,rch(p),f);//线段树基本操作 update(p); }
查询也差不多
int ask(int p,int l,int r,int length)//p是线段树,lr当前左右端点,length是给定长度 { spread(p); if(l==r) return l;//返回下标 int mid=l+r>>1; if(t[lch(p)].sum>=length)//如果最大值大于要求的长度 return ask(lch(p),l,mid,length);//看能不能找一个更左的 if(t[lch(p)].rm+t[rch(p)].lm>=length)//如果正好,就返回下标 return mid-t[lch(p)].rm+1; else return ask(rch(p),mid+1,r,length);//继续找最优 }
主函数的话,注意一下开房操作,只有当连续区间大于给定值才开房,(t[1].sum,大汇点的最大值)
还有,查询过后记得更新一次区间信息。
全代码:
#include<bits/stdc++.h> using namespace std; #define lch(x) x<<1 #define rch(x) x<<1|1 const int maxn=100010; int n,m; struct tree { int lm,rm,sum,add,len; }t[maxn<<2]; void build(int l,int r,int p) { t[p].sum=t[p].lm=t[p].rm=t[p].len=r-l+1; t[p].add=0; if(l==r) { return; } int mid=l+r>>1; build(l,mid,lch(p)); build(mid+1,r,rch(p)); } void spread(int p) { if(t[p].add==0) return; if(t[p].add==1) { t[lch(p)].add=t[rch(p)].add=1; t[lch(p)].sum=t[lch(p)].lm=t[lch(p)].rm=0; t[rch(p)].sum=t[rch(p)].lm=t[rch(p)].rm=0; } if(t[p].add==2) { t[lch(p)].add=t[rch(p)].add=2; t[lch(p)].sum=t[lch(p)].lm=t[lch(p)].rm=t[lch(p)].len; t[rch(p)].sum=t[rch(p)].lm=t[rch(p)].rm=t[rch(p)].len; } t[p].add=0; } void update(int p) { if(t[lch(p)].sum==t[lch(p)].len) { t[p].lm=t[lch(p)].len+t[rch(p)].lm; } else t[p].lm=t[lch(p)].lm; if(t[rch(p)].sum==t[rch(p)].len) { t[p].rm=t[rch(p)].len+t[lch(p)].rm; } else t[p].rm=t[rch(p)].rm; t[p].sum=max(max(t[lch(p)].sum,t[rch(p)].sum),t[lch(p)].rm+t[rch(p)].lm); } void change(int l,int r,int L,int R,int p,int f) { spread(p); if(L<=l&&r<=R) { if(f==1) { t[p].sum=t[p].lm=t[p].rm=0; } else { t[p].sum=t[p].lm=t[p].rm=t[p].len; } t[p].add=f; return; } int mid=l+r>>1; if(L<=mid)change(l,mid,L,R,lch(p),f); if(R>mid)change(mid+1,r,L,R,rch(p),f); update(p); } int ask(int p,int l,int r,int length) { spread(p); if(l==r) return l; int mid=l+r>>1; if(t[lch(p)].sum>=length) return ask(lch(p),l,mid,length); if(t[lch(p)].rm+t[rch(p)].lm>=length) return mid-t[lch(p)].rm+1; else return ask(rch(p),mid+1,r,length); } int main() { scanf("%d%d",&n,&m); build(1,n,1); while(m--) { int flag; scanf("%d",&flag); if(flag==1) { int x; scanf("%d",&x); if(t[1].sum>=x) { int ans=ask(1,1,n,x); printf("%d\n",ans); change(1,n,ans,ans+x-1,1,1); } else printf("0\n"); } if(flag==2) { int x,y; scanf("%d%d",&x,&y); change(1,n,x,x+y-1,1,2); } } return 0; }
(完)