POJ-3667 Hotel[线段树]

题意:

有连续的N间房间, 两种操作, 一是Check in, 要找D间连续的空房间出来(房号尽可能小), 输出第一个的位置, 如果没有就输出0;  二是Check out, 从x号房开始连续的D个房间, 重新变成空房间.

思路:

1/ 先说我开始的想法: N个房间当成1~N的线段, 我们肯定是要维护一个最值, 用这个最值来判断这个区间是否能满足D间连续的空房间. 自然我们会维护区间的最大连续空房间长度msum. 然后就想如何维护它, 重点在于pushup(e)操作. 即从子节点如何更新父亲结点. 不难想到,  父区间的最大连续值要么等于左子的最大连续值, 要么等于右子的, 如果都不是那么说明合并区间的时候有新的连续段产生, 必然是左子占一部分, 右子占一部分. 这样我们应该同时要维护区间的左端点连续段长left  跟  右端点连续段长right. 这样更新到父节点就可以写为 msum[父亲] = max{ right[左子]+left[右子], msum[左子],  msum[右子] }.

2/ 得出上面那三个结点主域其实不难. 但是接下来我又开始傻逼了:  我想, 题目求的是左端点的位置, 那我们是不是该维护最大连续空房间区间的左端点呢....然后为了维护这个端点...我们还得同时维护 left 的右端点 lidx, right的 左端点 ridx 吧.....(OMG......这种神马关于端点的傻逼想法已经不是第一次了....)

其实吧....仔细想想就知道这个端点是不必要也是无用的. 我们维护最大连续区间, 目的是用来判断要找的那个点在哪里, 而不是每次都返回最大连续区间的左端点(题目说的是尽可能选左边的区间, 而不是最大的).....这他妈其实是单点查询, 成段替换. (不是区间查询, 区间的信息只不过是让我们找到那个点, 这题是找到那个点后再成段替换一次).

3/ 这种题hh大牛把它归为区间合并. 通常是处理连续信息. 要关注左子的右部跟右子的左部.

4/ 这种要根据区间信息来找到某个点的题目: 如 HDOJ-2795 Billboard , 区间维护的是一个最值, 根据一个宽度跟最值的关系来找到那个点(类似二分). 而这题除了在左子, 在右子这两种情况外, 还有左子右子各占一部分这种情况....三分了 = =....


代码:

有维护几个如上述的域, 但是其实没用到(其实可以维护, 也可以用, 但是完全可以用表示长度的三个域替代...)....无视吧

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<string>
#include<vector>
#include<map>
#include<algorithm>
//using namespace std;
using std::cout;
using std::endl;
inline int Rint() { int x; scanf("%d", &x); return x; }
inline int max(int x, int y) { return (x>y)? x: y; }
inline int min(int x, int y) { return (x<y)? x: y; }
#define FOR(i, a, b) for(int i=(a); i<=(b); i++)
#define FORD(i,a,b) for(int i=(a);i>=(b);i--)
#define REP(x) for(int i=0; i<(x); i++)
typedef long long int64;
#define INF (1<<30)
const double eps = 1e-8;
#define bug(s) cout<<#s<<"="<<s<<" "

//	线段树+区间合并
//	区间替换(根据区间信息确定更新的区间)+维护区间最长连续区间, 端点连续最长区间(用于合并区间).
//	延迟标记, tobe 住或没住

//	注:		我开始以为要维护各种端点, 后来发现不用, 也是无用的......回来再总结...

#define MAXN 50002
//struct node
//{
//	int left, right;	//最左,最右 最大连续长度
//	int midx, msum;	//最长空房子起始点, 最长长度
//};
int left[MAXN<<2], right[MAXN<<2];
int lidx[MAXN<<2], ridx[MAXN<<2];		//位置???要不要?
int midx[MAXN<<2], msum[MAXN<<2];
int tobe[MAXN<<2];		//lazy

int n;

void pushup(int l, int r, int e)
{
	int mid = (l+r)>>1;
	left[e] = left[e<<1];
	right[e] = right[e<<1|1];
	//if(lidx[e<<1]+1 == ridx[e<<1|1]) left[e]  = right[e] =  left[e<<1]+right[e<<1|1];		//占了整个区间
	//if(left[e<<1]==(mid-l+1) && right[e<<1|1]==(r-mid)) left[e]  = right[e] =  left[e<<1]+right[e<<1|1];		//占了整个区间

	//	光算占满(l, r)的情况片面了,  就算不占满(l, r) 占满(l, mid+x) 那left也是要更新的!!
	if(left[e]==(mid-l+1)) left[e]+=left[e<<1|1];
	if(right[e] == (r-mid)) right[e]+=right[e<<1];

	if(msum[e<<1] > msum[e<<1|1])
		msum[e] = msum[e<<1], midx[e] = midx[e<<1];
	else
		msum[e] = msum[e<<1|1], midx[e] = midx[e<<1|1];
	if(msum[e] < right[e<<1]+left[e<<1|1])
		msum[e] = right[e<<1]+left[e<<1|1], midx[e] = ridx[e<<1];

	//bug(l);bug(r);bug(msum[e])<<endl;
	//bug(left[e]);bug(right[e])<<endl;
}
void pushdown(int l, int r, int e)		//使子节点维护完全
{
	if(tobe[e])		//-1表示撤离, 1表示住进
	{
		int mid = (l+r)>>1;
		msum[e<<1] = left[e<<1] = right[e<<1] = tobe[e]==-1? (mid-l+1): 0;
		
		lidx[e<<1] = tobe[e]==-1? mid: -1;
		ridx[e<<1] = tobe[e]==-1? l: -1;
		
		msum[e<<1|1] = left[e<<1|1] = right[e<<1|1] = tobe[e]==-1? (r-mid): 0;

		lidx[e<<1|1] = tobe[e]==-1? r: -1;
		ridx[e<<1|1] = tobe[e] == -1? mid+1: -1;
		midx[e<<1] = tobe[e]==-1? l: -1;
		midx[e<<1|1] = tobe[e]==-1? mid+1: -1;

		tobe[e<<1] = tobe[e<<1|1]  = tobe[e];
		tobe[e] = 0;
		
		//cout<<"pushdown  ";bug(l);bug(r);bug(msum[e])<<endl;
	}
}
void build(int l,int r,int e)
{
	msum[e] =left[e]=right[e]=0;
	lidx[e]=ridx[e]=midx[e] = -1;
	tobe[e] = 0;
	if(l==r)
	{
		msum[e] = right[e] = left[e] = (r-l+1);
		//bug(l);bug(r);bug(msum[e]);bug(right[e]);bug(left[e])<<endl;
		ridx[e] = midx[e] = l;
		lidx[e] = r;
	}
	else
	{
		int mid = (l+r)>>1;
		build(l, mid, e<<1);
		build(mid+1, r, e<<1|1);
		pushup(l,r,e);
	}
}
void update(int L, int R, int v, int l, int r, int e)
{
	//bug(L);bug(R)<<endl;
	if(L<=l && r<=R)
	{
		tobe[e] = v;
		msum[e] = left[e] = right[e] = v==-1? r-l+1: 0;
		ridx[e] = midx[e] = v==-1? l: -1;
		lidx[e] = v==-1? r: -1;
	}
	else
	{
		pushdown(l, r, e);
		int mid = (l+r)>>1;
		if(L<=mid)
			update(L,R, v, l, mid, e<<1);
		if(mid+1<=R)
			update(L, R, v, mid+1, r, e<<1|1);
		pushup(l,r,e);
	}
}

//这种都是根据一个值来确定你所 真正要查找的区间(点)
int query(int w, int l, int r, int e)		//这个query要结合更新?.... 也可以在外面做
{
	if(l==r)		//实际上这个是查点, 然后再更新区间
	{
		return l;
	}
	else
	{
		pushdown(l, r, e);
		int mid=(l+r)>>1;
		if(msum[e<<1]>=w)	//左区间满足吗?
			return query(w, l, mid, e<<1);
		else if(right[e<<1]+left[e<<1|1]>=w)	//跨域左右满足?	//我又一次忘记合并答案哦?....卧槽
			return mid-right[e<<1]+1;				//!!!!!!!
		//else if(msum[e<<1|1]>=w)
		return query(w, mid+1, r, e<<1|1);
	}
}

/*				OH..NO..!! 我终于明白了, 记录midx(最大连续区间的左端点), 是不够的, 因为它是要找满足的最左边的点.
int query(int w, int l, int r, int e)		//其实每次都是整块区间区间查询(既然我已经把midx记录下来了, 就不用再找到那个点)
{
	if(msum[1]<w) return 0;
	
}
*/
int main()
{
	int q;
	while(scanf("%d%d", &n, &q)!=EOF)
	{
		build(1, n, 1);
		REP(q)
		{
			int op = Rint();
			int x  = Rint();
			if(op == 1)
			{
				int idx;
				if(msum[1]<x) puts("0");
				else 
				{
					printf("%d\n", idx=query(x, 1, n, 1));
					// 更新
					update(idx, idx+x-1, 1, 1, n, 1);
				}
			}
			else
			{
				int y = Rint();
				//update(x, y, -1, 1, n, 1);		//注意题目啊, 区间是 (x, x+y-1) 才对, y是长度
				update(x, x+y-1, -1, 1, n, 1);
			}
		}
	}
}


发布了179 篇原创文章 · 获赞 5 · 访问量 20万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览