参考了给老师的代码
题意:
给出n,m(1<=N,M<=50000),表示一个有N个空位的数列,对其进行M次操作。有这两种操作:
1 Di:表示将数列中最左端的连续Di个空位填满,输出被填满区间左端点的位置,若无法做到则不填并输出0。
2 Xi Di:将区间[Xi,Xi+Di-1]清空。
例:如果输入1 3,则:
输出:1
再输入1 2,则:
再输入2 2 3,则清空[2,2+3-1]的部分:
问题模型:
核心:维护一个区间的最长连续1(这个1代表的是空位)
那为了维护一个区间的最长连续1,就需要两个额外信息,从左端点开始的最长连续1和从右端点开始的最长连续1!这样在区间合并的时候,才能获得新区间真正的最长连续1。
举个例子:[1,5] 和[6,10]要合并,如果你只知道这个区间的最长连续1,你能合并吗?所以我们要构造如下的线段树结构,其中
我们有有三种操作:
- 1.将[a, b]中的所有数字改成0
- 2.将[a, b]中的所有数字改成1
- 3.询问[a, b]中最长连续的1的长度是多少
前两种修改区间的操作有三种情况:
- 1.放在左儿子那个区间。
- 2.放在右儿子那个区间。
- 3.放在左右儿子中间,就是占用左区间的右部分和右区间的左部分。
要求查询时线段树要能直接获取区间的最大连续长度,需要维护四个值,分别是:
- lsum记录该区间左端点开始的最长连续的值为1区间,用来表示左区间最长连续空房数
- rsum记录该区间右端点开始的最长连续的值为1区间,用来表示右区间最长连续空房数
- sum记录该区间内最长连续的值为1的区间,用来表示用区间最长连续空房数
- cover形象解释就是记录区间的“颜色”,具体操作是当这个区间全部是 1时color置1,全部为0时color置0,否则置-1。在pushup()的时候会用到。
向下更新策略:
先向下转移标记cover,在通过标记更新相应的数据。
向上更新策略:
- sum=max(左儿子的sum,右儿子的sum,左右儿子中间部分)
- lsum=左儿子的lsum,如果左儿子为空还要加上右儿子的lsum(所谓的左儿子为空事实上等于左儿子等于区间长度)
- rsum=右儿子的rsum,如果右儿子为空还要加上左儿子的rsum
思路:
- 利用线段树建立模型,维护最大连续区间长度,其中区间长度就是对应的房间数目,并且对应区间中最左边的断点就是answer。
- 同时因为需要求出连续区间的最大长度,因此每次PushUp时都将左右区间合并,lsum维护左区间的最大长度,rsum维护右区间的最大长度,msum维护区间1…N中的最大连续区间长度。
- cover标志对应区间是否为空(没有住客)。cover表示区间状态如下:
0:区间全空。(区间里面全都是1,这个太坑爹了),1:区间全满。(区间全都是0),-1:当前区间已初始化,不用向下更新。
#include<iostream>
#include<string.h>
#include<algorithm>
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
const int maxn = 55555<<1;
struct Hotel
{
int lsum;//最长左1
int rsum;//最长右1
int sum;//最长连续1
int cover;
} hotel[maxn<<2];
void pushUp(int rt,int m)//因为区间赋值需要区间长度作为参数,所以这里需要一个m
{
hotel[rt].lsum = hotel[ls].lsum;
hotel[rt].rsum = hotel[rs].rsum;
if(hotel[rt].lsum==m-(m>>1))//所谓的左儿子为空事实上是做儿子等于区间长度,即区间内部全都是1
hotel[rt].lsum+=hotel[rs].lsum;
if(hotel[rt].rsum==(m>>1))//所谓的左儿子为空事实上是做儿子等于区间长度
hotel[rt].rsum+=hotel[ls].rsum;
hotel[rt].sum = max(hotel[ls].rsum+hotel[rs].lsum,max(hotel[ls].sum,hotel[rs].sum));
}
void pushDown(int rt,int m)
{
if(hotel[rt].cover!=-1)
{
hotel[rs].cover = hotel[ls].cover = hotel[rt].cover;//将cover向下推
hotel[ls].sum = hotel[ls].lsum = hotel[ls].rsum = hotel[rt].cover?0:m-(m>>1);//得到左边的和,要不为空,要不就是m-m/2
hotel[rs].sum = hotel[rs].lsum = hotel[rs].rsum = hotel[rt].cover?0:(m>>1);//得到右边的和
hotel[rt].cover = -1;
}
}
void build(int l,int r,int rt)
{
hotel[rt].lsum = hotel[rt].rsum = hotel[rt].sum = r-l+1;
hotel[rt].cover = -1;//当前空间已初始化,不用向下更新
if(l==r)
return ;
int m = (l+r)>>1;
build(l,m,ls);
build(m+1,r,rs);
}
void update(int L,int R,int C,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
hotel[rt].cover = C;
hotel[rt].sum = hotel[rt].rsum = hotel[rt].lsum = hotel[rt].cover?0:r-l+1;
return ;
}
int m = (r+l)>>1;
pushDown(rt,r-l+1);
if(L<=m)
update(L,R,C,l,m,ls);
if(R>m)
update(L,R,C,m+1,r,rs);
pushUp(rt,r-l+1);
}
int query(int w,int l,int r,int rt)//w代表要删除的数据?
{
if(l==r)
return l;
pushDown(rt,r-l+1);
int m = (l+r)>>1;
if(hotel[ls].sum>=w)
return query(w,l,m,ls);//.sum代表空房数量,如果空房数量足够,优先往靠前的走
else if(hotel[ls].rsum+hotel[rs].lsum>=w)
return m-hotel[ls].rsum+1;//有可能空房部分在中间
return query(w,m+1,r,rs);
}
int main()
{
//freopen("poj 3667.txt","r",stdin);
int n,k;
while(scanf("%d%d",&n,&k)!=EOF)
{
build(1,n,1);
while(k--)
{
//for(int i = 1;i<=17;++i)
//printf("%d ",hotel[i].sum);
//printf("\n");
int op,a,b;
scanf("%d%d",&op,&a);
if(op==1)
{
if(hotel[1].sum<a)
{
printf("0\n");
continue;
}
int p = query(a,1,n,1);
printf("%d\n",p);
update(p,p+a-1,1,1,n,1);
}
else
{
scanf("%d",&b);
update(a,a+b-1,0,1,n,1);//输入的5 5代表清除5~9
}
}
}
return 0;
}