题意:
有连续的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);
}
}
}
}