题面
【题目描述】
奶牛们最近的旅游计划,是到苏必利尔湖畔,享受那里的湖光山色,以及明媚的阳光。作为整个旅游的策划者和负责人,贝茜选择在湖边的一家著名的旅馆住宿。这个巨大的旅馆一共有
N
(
1
<
=
N
<
=
50
,
000
)
N (1 <= N <= 50,000)
N(1<=N<=50,000)间客房,它们在同一层楼中顺次一字排开,在任何一个房间里,只需要拉开窗帘,就能见到波光粼粼的湖面。 贝茜一行,以及其他慕名而来的旅游者,都是一批批地来到旅馆的服务台,希望能订到
D
i
(
1
<
=
D
i
<
=
N
)
D_i (1 <= D_i <= N)
Di(1<=Di<=N)间连续的房间。服务台的接待工作也很简单:如果存在
r
r
r满足编号为
r
.
.
r
+
D
i
−
1
r..r+D_i-1
r..r+Di−1的房间均空着,他就将这一批顾客安排到这些房间入住;如果没有满足条件的r,他会道歉说没有足够的空房间,请顾客们另找一家宾馆。如果有多个满足条件的
r
r
r,服务员会选择其中最小的一个。 旅馆中的退房服务也是批量进行的。每一个退房请求由2个数字
X
i
、
D
i
X_i、D_i
Xi、Di描述,表示编号为
X
i
.
.
X
i
+
D
i
−
1
(
1
<
=
X
i
<
=
N
−
D
i
+
1
)
X_i..X_i+D_i-1 (1 <= X_i <= N-D_i+1)
Xi..Xi+Di−1(1<=Xi<=N−Di+1)房间中的客人全部离开。退房前,请求退掉的房间中的一些,甚至是所有,可能本来就无人入住。 而你的工作,就是写一个程序,帮服务员为旅客安排房间。你的程序一共需要处理
M
(
1
<
=
M
<
50
,
000
)
M (1 <= M < 50,000)
M(1<=M<50,000)个按输入次序到来的住店或退房的请求。第一个请求到来前,旅店中所有房间都是空闲的。
【输入】
第
1
1
1行:
2
2
2个用空格隔开的整数:
N
、
M
N、M
N、M
第
2..
M
+
1
2..M+1
2..M+1行: 第i+1描述了第
i
i
i个请求,如果它是一个订房请求,则用
2
2
2个数字
1
、
D
i
1、D_i
1、Di描述,数字间用空格隔开;如果它是一个退房请求,用
3
3
3 个以空格隔开的数字
2
、
X
i
、
D
i
2、X_i、D_i
2、Xi、Di描述
【输出】
对于每个订房请求,输出
1
1
1个独占
1
1
1行的数字:如果请求能被满足 ,输出满足条件的最小的
r
r
r;如果请求无法被满足,输出
0
0
0.
【样例输入】
10 6
1 3
1 3
1 3
1 3
2 5 5
1 6
【样例输出】
1
4
7
0
5
算法分析
我们可以将题目简化,看作
N
N
N个数的
0
、
1
0、1
0、1序列,
0
0
0表示无人入住,
1
1
1表示入住。
现在有两种操作:
入住:寻找连续的
D
D
D个
0
0
0的区间,然后都修改为
1
1
1;
退房:将区间
[
X
,
X
+
D
−
1
]
[X,X+D-1]
[X,X+D−1]全部变为
0
0
0。
将区间修改为
1
,
0
1,0
1,0,这是线段树的基本操作,区间修改。
现在问题在于如何求解连续的
D
D
D个
0
0
0的区间。
对于结点
k
k
k,表示区间
[
l
,
r
]
[l,r]
[l,r]维护三个数组:
l
m
[
k
]
lm[k]
lm[k]——为左端点
l
l
l开始最大连续
0
0
0的长度.
r
m
[
k
]
rm[k]
rm[k]——为右端点
r
r
r开始最大连续
0
0
0的长度.
m
[
k
]
m[k]
m[k]——为区间
[
l
,
r
]
[l,r]
[l,r]最大连续
0
0
0的长度.
我们可以通过这三个数组知道我们需要的信息。
(1)初始时,对于结点
k
k
k:
lm[k]=rm[k]=m[k]=r-l+1
(2)求解
l
m
[
]
、
r
m
[
]
lm[]、rm[]
lm[]、rm[]
根据上图,可以发现:
如果
l
m
[
k
∗
2
]
=
=
(
m
i
d
−
l
+
1
)
,
l
m
[
k
]
=
l
m
[
k
∗
2
]
+
l
m
[
k
∗
2
+
1
]
lm[k*2]==(mid-l+1) ,lm[k]=lm[k*2]+ lm[k*2+1]
lm[k∗2]==(mid−l+1),lm[k]=lm[k∗2]+lm[k∗2+1]
如果
l
m
[
k
∗
2
]
!
=
(
m
i
d
−
l
+
1
)
,
l
m
[
k
]
=
l
m
[
k
∗
2
]
;
lm[k*2]!=(mid-l+1) ,lm[k]=lm[k*2];
lm[k∗2]!=(mid−l+1),lm[k]=lm[k∗2];
如果
r
m
[
k
∗
2
+
1
]
=
=
(
r
−
m
i
d
)
,
r
m
[
k
]
=
r
m
[
k
∗
2
+
1
]
+
r
m
[
k
∗
2
]
;
rm[k*2+1]==(r-mid) ,rm[k]=rm[k*2+1]+rm[k*2];
rm[k∗2+1]==(r−mid),rm[k]=rm[k∗2+1]+rm[k∗2];
如果
r
m
[
k
∗
2
+
1
]
!
=
(
r
−
m
i
d
)
,
r
m
[
k
]
=
r
m
[
k
∗
2
+
1
]
;
rm[k*2+1]!=(r-mid) ,rm[k]=rm[k*2+1];
rm[k∗2+1]!=(r−mid),rm[k]=rm[k∗2+1];
(3)求解
m
[
]
m[]
m[]
m[k]取最大值有多种可能,在所有可能情况下取最大值:
m
[
k
]
=
m
a
x
m[k]=max
m[k]=max{
l
m
[
k
]
,
r
m
[
k
]
,
r
m
[
2
k
]
+
l
m
[
2
k
+
1
]
,
m
[
2
k
]
,
m
[
2
k
+
1
]
lm[k] , rm[k] , rm[2k]+lm[2k+1],m[2k], m[2k+1]
lm[k],rm[k],rm[2k]+lm[2k+1],m[2k],m[2k+1] }
询问连续为
0
0
0的长度
D
D
D的最小起点编号:
m
[
k
]
m[k]
m[k]可以判断区间是否存在,但不能确定起点
但是当出现连续的区间是由左孩子和右孩子组成的,即
r
m
[
2
k
]
+
l
m
[
2
k
+
1
]
>
=
D
rm[2k]+lm[2k+1]>=D
rm[2k]+lm[2k+1]>=D时,就能够确定起点为:
m
i
d
−
r
m
[
2
k
]
+
1
,
m
i
d
=
(
l
+
r
)
/
2
mid-rm[2k]+1,mid=(l+r)/2
mid−rm[2k]+1,mid=(l+r)/2。因此递归寻找即可,肯定会出现这种情况或者到底叶节点。
参考程序
#include<bits/stdc++.h>
#define N 500010
using namespace std;
int n;
int s[N*4],lm[N*4],rm[N*4],m[N*4],lazy[N*4];
//lm为左端点开始最大连续0的长度
//rm为右端点...
//m为区间为0的最大连续长度 。都可以通过孩子结点求得
void built(int k,int l,int r)
{
lm[k]=rm[k]=m[k]=r-l+1;//初始化
if(l==r) return;
int mid=(l+r)/2;
built(k*2,l,mid);
built(k*2+1,mid+1,r);
}
void pushdown(int k,int l,int r)
{
if(lazy[k]==1) //1为全部占用
{
lm[k*2]=rm[k*2]=m[k*2]=0;
lm[k*2+1]=rm[k*2+1]=m[k*2+1]=0;
lazy[k*2]=lazy[k*2+1]=1;
}
if(lazy[k]==2)//2为全部退房
{
int mid=(l+r)/2;
lm[k*2]=rm[k*2]=m[k*2]=mid-l+1;
lm[k*2+1]=rm[k*2+1]=m[k*2+1]=r-mid;
lazy[k*2]=lazy[k*2+1]=2;
}
lazy[k]=0;
}
int ask(int k,int l,int r,int len)
{
if(l==r)
{
if(len==1) return l;
else return 0;
}
if(lazy[k]) pushdown(k,l,r);
int mid=(l+r)/2;
if(m[k*2]>=len) return ask(k*2,l,mid,len);//全部在左孩子里取
if(rm[k*2]+lm[k*2+1]>=len) return mid-rm[k*2]+1;//左右孩子各取一部分
else return ask(k*2+1,mid+1,r,len);//全部在右孩子里取
}
void f(int k,int l,int r,int x,int y,int t)//对区间[x,y]进行处理
{
if(l>y||r<x) return;
if(x<=l&&r<=y)
{
if(t==1)
lm[k]=rm[k]=m[k]=0;
if(t==2)
lm[k]=rm[k]=m[k]=r-l+1;
lazy[k]=t;
pushdown(k,l,r);
return;
}
int mid=(l+r)/2;
if(lazy[k]) pushdown(k,l,r);
f(k*2,l,mid,x,y,t);
f(k*2+1,mid+1,r,x,y,t);
if(lm[k*2]==(mid-l+1)) lm[k]=lm[k*2]+lm[k*2+1];
else lm[k]=lm[k*2];
if(rm[k*2+1]==(r-mid)) rm[k]=rm[k*2+1]+rm[k*2];
else rm[k]=rm[k*2+1];
int maxn;
maxn=max(m[k*2],m[k*2+1]);
maxn=max(maxn,rm[k*2]+lm[k*2+1]);
maxn=max(maxn,lm[k]);
maxn=max(maxn,rm[k]);
m[k]=maxn;
}
int main()
{
int q;
scanf("%d%d",&n,&q);
built(1,1,n);
int x,y,d;
for(int i=1;i<=q;i++)
{
scanf("%d",&x);
if(x==1)
{
scanf("%d",&d);
int ans=ask(1,1,n,d);
printf("%d\n",ans);
if(ans!=0) f(1,1,n,ans,ans+d-1,1); //找到并入住更新
}
else
{
scanf("%d%d",&y,&d);
f(1,1,n,y,y+d-1,2);
}
}
return 0;
}