旅馆

【问题描述】

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的查询修改操作(差点写错),

写完了细心调试,看一下所有的变量值是否都跟预期中的一样,

还有对拍。。。