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