説明文
牛は北に向かってカナダのサンダーベイに向かい、文化的な豊かさを得て、日当たりの良いスペリオル湖の海岸で休暇を楽しみます。かつて有能な旅行代理店であったベシーは、有名なカンバーランドストリートにあるブルムースホテルを休暇用の邸宅として指定しました。この巨大なホテルには、N(1≤N≤50,000)の部屋がすべて非常に長い廊下の同じ側にあります(もちろん湖を見た方がいいです)。牛や他の訪問者は、サイズDi(1 ≤Di≤N)で、フロントデスクに近づいてチェックインします。各グループiは、カウンターにスタッフがいるムースであるCanmuuに、Di隣接する部屋のセットを要求します。利用可能な場合は、いくつかの連続した部屋番号r..r + Di-1のセットを割り当てます。また、隣接する部屋のセットがない場合は、代替の宿泊施設を丁寧に提案します。Canmuuは常に最小のrの値を選択します。訪問者はまた、隣接する部屋のグループからホテルを出発します。チェックアウトiには、部屋Xi ..Xi + Di-1(1≤Xi≤N-Di + 1)の空席を指定するパラメーターXiおよびDiがあります。これらの部屋の一部(またはすべて)は、チェックアウト前は空である可能性があります。あなたの仕事は、M(1≤M <50,000)チェックイン/チェックアウトリクエストを処理することによってCanmuuを支援することです。当ホテルは最初は空いています。
入力
*行1:スペースで区切られた2つの整数:NおよびM
*行2..M + 1:行i + 1には、次の2つの可能な形式のいずれかで表現された要求が含まれます:(a)チェックイン要求を表す2つのスペースで区切られた整数: 1およびDi(b)チェックアウトを表すスペースで区切られた3つの整数:2、Xi、およびDi
出力
* Lines 1 .....:チェックインリクエストごとに、占有される部屋の連続するシーケンスの最初の部屋である単一の整数rを使用して、単一の行を出力します。要求を満たせない場合は、0を出力します。
入力例
10 6
1 3
1 3
1 3
1 3
2 5 5
1 6
出力例
1
4
7
0
5
主なアイデア:
最初は完全に空のN部屋のホテルがあります。その後の操作はMです。操作には2つのタイプがあります。1Dを入力することはDに移動することを意味し、リクエストに割り当てられた部屋番号は連続しています。空の連続する部屋がある場合は、条件を満たす最小の部屋番号を出力します。それ以外の場合は0を出力します(つまり、連続するD部屋はありません)。2XDを入力します。これは、部屋番号がX ... X + D + 1であり、全員が去る。
分析:
トピックにはインターバル操作が含まれます。ラインセグメントツリーを使用してモデルを構築することを検討してください。ラインセグメントツリーのノードは、合計を使用して、間隔内の最大の連続アイドル間隔の長さを維持します。lsumは、間隔[l、r]をlから維持し、連続アイドル間隔の右側までの長さを維持します。左側の連続するフリーインターバルの長さ(ノードインターバルにまたがるインターバルを維持しやすい)。遅延マーカーを使用して、更新の深さを減らします。ゲストが離れたら、フリーゾーンを上にマージします。
具体的な説明については、コードを参照してください。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
#include <set>
#include <cstdio>
#include <map>
#include <iomanip>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
struct segtree{
int l,r;
int lsum,rsum,sum;//lsum从左端开始连续的区间长度,rsum从右端开始连续的区间长度
int tag;//延迟标记
};
segtree t[50005*4];
void build(int p,int l,int r){//建树
int mid=(l+r)/2;
t[p].l=l;
t[p].r=r;
t[p].lsum=t[p].rsum=t[p].sum=r-l+1;
t[p].tag=-1;
if(l==r) return;
build(p*2,l,mid);
build(2*p+1,mid+1,r);
}
void pushdown(int p){//将父节点的状态向下传递(延迟标记的传递)
if(t[p].tag!=-1){//说明父节点的状态已被改变
int lson=p*2,rson=p*2+1;
t[lson].tag=t[rson].tag=t[p].tag;
if(t[p].tag==1){//父节点的区间被占用
t[lson].lsum=t[lson].rsum=t[lson].sum=0;
t[rson].lsum=t[rson].rsum=t[rson].sum=0;
}
else{//父节点的区间空闲
t[lson].lsum=t[lson].rsum=t[lson].sum=(t[lson].r-t[lson].l+1);
t[rson].lsum=t[rson].rsum=t[rson].sum=(t[rson].r-t[rson].l+1);
}
t[p].tag=-1;//父节点传递完成后标志复原
}
}
int query(int len,int p){
if(t[p].l==t[p].r){
return t[p].l;
}
pushdown(p);//首先将本节点的状态传递下去,便于接下来的递归查询
int lson=p*2;
int rson=p*2+1;
if(t[lson].sum>=len){//如果左侧的空闲长度足够,先检测左侧(保证最小的编号)
return query(len,lson);
}
else if(t[lson].rsum+t[rson].lsum>=len){//考虑跨越左右两侧的区间
return (t[p].l+t[p].r)/2-t[lson].rsum+1;
}
else{//检测右侧
return query(len,rson);
}
}
void pushup(int p){//区间合并
int lson=p*2,rson=p*2+1;
t[p].lsum=t[lson].lsum;
t[p].rsum=t[rson].rsum;
//进一步更新lsum和rsum
if(t[lson].lsum==(t[lson].r-t[lson].l+1)) t[p].lsum+=t[rson].lsum;
if(t[rson].rsum==(t[rson].r-t[rson].l+1)) t[p].rsum+=t[lson].rsum;
//sum取三种情况下的最大值
t[p].sum=max(t[lson].rsum+t[rson].lsum,max(t[lson].sum,t[rson].sum));
}
void update(int p,int L,int R,int flag){//flag=1表示占用,flag=0表示空闲
int lson=p*2,rson=p*2+1;
if(L<=t[p].l&&R>=t[p].r){//区间被完全覆盖
t[p].tag=flag;
t[p].lsum=t[p].rsum=t[p].sum=(flag?0:(t[p].r-t[p].l+1));
return;//由于此处直接返回,所以需要延迟标记,此处未更新该节点的子树
}
pushdown(p);//进入子节点前先传递延迟标记,便于接下来的递归更新
int mid=(t[p].l+t[p].r)/2;
if(L<=mid){
update(lson,L,R,flag);
}
if(R>mid){
update(rson,L,R,flag);
}
pushup(p);//子节点更新返回后要更新父节点
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
build(1,1,n);
rep(i,1,m){
int com;
scanf("%d",&com);
if(com==1){
int d;
scanf("%d",&d);
if(d>t[1].sum) printf("0\n");//如果需要入住的量大于整个区间内的连续空闲区间,则无法安排
else{
int ans=query(d,1);
printf("%d\n",ans);
update(1,ans,ans+d-1,1);//记得要更新
}
}
else{
int x,d;
scanf("%d%d",&x,&d);
update(1,x,x+d-1,0);
}
}
return 0;
}