旅馆
【问题描述】
OIER 们最近的旅游计划,是到长春净月潭,享受那里的湖光山色,以及明 媚的阳光。你作为整个旅游的策划者和负责人,选择在潭边的一家著名的旅馆住 宿。这个巨大的旅馆一共有 N (1 <= N <= 50000)间客房,它们在同一层楼中顺次 一字排开,在任何一个房间里,只需要拉开窗帘,就能见到波光粼粼的潭面。 所有的旅游者,都是一批批地来到旅馆的服务台,希望能订到 Di (1 <= Di <= N)间连续的房间。服务台的接待工作也很简单:如果存在 r 满足编号为 r..r+Di-1 的房间均空着,他就将这一批顾客安排到这些房间入住;如果没有满足条件的 r, 他会道歉说没有足够的空房间,请顾客们另找一家宾馆。如果有多个满足条件的 r,服务员会选择其中最小的一个。 旅馆中的退房服务也是批量进行的。每一个退房请求由 2 个数字 Xi、Di 描 述,表示编号为 Xi..Xi+Di-1 (1 <= Xi <= N-Di+1)房间中的客人全部离开。退房前, 请求退掉的房间中的一些,甚至是所有,可能本来就无人入住。 你的工作,就是写一个程序,帮服务员为旅客安排房间。你的程序一共需要 处理 M (1 <= M <=50000)个按输入次序到来的住店或退房的请求。第一个请求到 来前,旅店中所有房间都是空闲的。
【输入格式】 从文件 hotel.in 中输入数据。 第 1 行: 2 个用空格隔开的整数:N、M 第 2..M+1 行: 第 i+1 描述了第 i 个请求,如果它是一个订房请求,则用 2 个 数字 1、Di 描述,数字间用空格隔开;如果它是一个退房请求,用 3 个以空格隔 开的数字 2、Xi、Di 描述。
【输出格式】 输出到文件 hotel.out 中。 第 1..??行: 对于每个订房请求,输出 1 个独占 1 行的数字:如果请求能被 满足,输出满足条件的最小的 r;如果请求无法被满足,输出 0。
题解
看题第一眼——这不是USACO的Hotel吗?
线段树裸题。。。
房间都是一字排开的,我们就对房间建一棵线段树,
线段树上每个节点维护三个信息le,ri,al和一个懒标记lazy
le表示当前线段的左端点向右走,最多有多少个连续的空房间
ri表示当前线段的右端点向左走,最多有多少个连续的空房间
al表示当前线段最多有多少个连续的空房间
就可以愉快地合并啦
注意一下左端点的连续空房间可以一直延伸到右子区间(右端点也是),判一下就好啦。。。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 50005
#define lc i<<1
#define rc i<<1|1
struct node{
int l,r,lazy;
int le,ri,al;
int len(){return r-l+1;}
}a[N<<2];
void pushup(int i)//合并左右子区间
{
a[i].le=a[lc].le;
a[i].ri=a[rc].ri;
a[i].al=max(a[lc].ri+a[rc].le,max(a[lc].al,a[rc].al));
if(a[lc].len()==a[lc].le)//跨子区间
a[i].le=a[lc].le+a[rc].le;
if(a[rc].len()==a[rc].ri)//跨子区间
a[i].ri=a[rc].ri+a[lc].ri;
a[i].al=max(a[i].al,max(a[i].le,a[i].ri));
}
void pushdown(int i)
{
if(a[i].l==a[i].r){a[i].lazy=0;return;}
if(a[i].lazy==1){
a[lc].lazy=1;
a[lc].le=a[lc].ri=a[lc].al=a[lc].len();
a[rc].lazy=1;
a[rc].le=a[rc].ri=a[rc].al=a[rc].len();
a[i].lazy=0;
}
if(a[i].lazy==2){
a[lc].lazy=2;
a[lc].le=a[lc].ri=a[lc].al=0;
a[rc].lazy=2;
a[rc].le=a[rc].ri=a[rc].al=0;
a[i].lazy=0;
}
}
void build(int i,int l,int r)
{
a[i].l=l;a[i].r=r;a[i].lazy=0;
a[i].le=a[i].ri=a[i].al=a[i].len();
if(l==r) return;
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(i);
}
void insert(int i,int l,int r,int k)
{
if(a[i].l>r||a[i].r<l) return;
pushdown(i);
if(l<=a[i].l&&a[i].r<=r){
a[i].lazy=k;
if(k==1) a[i].le=a[i].ri=a[i].al=a[i].len();
else a[i].le=a[i].ri=a[i].al=0;
return;
}
insert(lc,l,r,k);
insert(rc,l,r,k);
pushup(i);
}
int ans;
void query(int i,int k)
{
if(a[i].al<k||ans) return;
pushdown(i);
if(a[i].l==a[i].r){
ans=a[i].l;
return;
}
if(a[lc].al>=k)
query(lc,k);
else if(a[lc].ri+a[rc].le>=k)//其实这里简化了许多,这里把在左、中、右的跨子区间的情况都考虑了
ans=a[lc].r-a[lc].ri+1;
else
query(rc,k);
}
int main()
{
//freopen("hotel.in","r",stdin);
//freopen("hotel.out","w",stdout);
int n,m,i,l,r,k,op;
n=gi();m=gi();
build(1,1,n);
for(i=1;i<=m;i++){
op=gi();
if(op==1){
k=gi();
ans=0;query(1,k);
printf("%d\n",ans);
if(ans!=0)
insert(1,ans,ans+k-1,2);
}
else{
l=gi();r=gi();
insert(1,l,l+r-1,1);
}
}
}
感想
遇到线段树的题要想清楚每个节点应该维护什么信息,
合并的时候仔细分类讨论,
写完了一定要出最大数据来检测自己有没有爆空间,
注意pushdown的边界(否则会RE)
注意长度为1的查询修改操作(差点写错),
写完了细心调试,看一下所有的变量值是否都跟预期中的一样,
还有对拍。。。