原题连接:http://poj.org/problem?id=3667
题意:参考样例,第一行输入n,m ,n代表有n个房间,编号为1---n,开始都为空房,m表示以下有m行操作,以下 每行先输入一个数 i ,表示一种操作:
若i为1,表示查询房间,再输入一个数x,表示在1--n 房间中找到长度为n的连续空房,输出连续n个房间中左端的房间号,尽量让这个房间号最小,若找不到长度为n的连续空房,输出0。
若i为2,表示退房,再输入两个数 x,y 代表 房间号 x---x+y-1 退房,即让房间为空。
思路:
昨天开始学习线段树的区间合并问题,看这个题时,没一点思路,网上搜各种博客,发现大部分都是贴代码,思路说的很少,以至于我昨天下午到晚上还没把题和线段树联系起来,弱爆了……= = ,今天lky给发了个好点的讲解博客,终于看懂了,写代码时,一个低级错误折磨了我一天,找了一天的错……唉,各种弱……详细说下这题过程吧。
一:存线段树数据的数组至少要有四个变量,也可六个(以六个为例)。
tree[ t ] 中 l r 分别表示 该节点的区间(这两个可以不要),lsum 存的是 从本节点区间最左端开始(向右)一共有lsum个连续的空房间,rlsum 存的是 从本节点区间最右端开始(向左)一共有lsum个连续的空房间,sum存的是 本区间一共最多有 sum 个连续的空房间,loop 的值有三种 -1,0,1,延时标记,-1代表不需要操作,0代表要将此区间置空,1代表要将此区间置满,注意loop延时标记用的,做过线段数的插线问线的延时标记这点就不难理解了。
二:关于Push_up函数
void Push_up(int l,int r,int t) { tree[t].lsum=tree[t<<1].lsum; //p---lsum tree[t].rsum=tree[t<<1|1].rsum; int x=(r+l)/2; if(tree[t].lsum==x-l+1) tree[t].lsum+=tree[t<<1|1].lsum; //p---lsum if(tree[t].rsum==r-x) tree[t].rsum+=tree[t<<1].rsum; tree[t].sum=Find_Max(tree[t<<1].sum,tree[t<<1|1].sum,tree[t<<1].rsum+tree[t<<1|1].lsum); }
就是当更新树中一节点后 回溯更新该节点的父节点的 lsum rsum sum的值。。
我们可以发现 节点 t 的lsum rsum sum 值是与它左右儿子节点的lsum rsum sum值有关系的t 节点的lsum 值 等于 他左儿子的lsum值 或 左儿子的lsum值 + 右儿子的lsum值 具体参考代码理解
同理:rsum值也一样……
对于 t 节点 的sum值等于 Max( 左儿子的sum值,右儿子的sum值 ,左儿子的rsum值+右儿子的lsum值)
三:关于Push_down函数
void Push_down(int t) { if(tree[t].loop!=-1) { tree[t<<1].loop=tree[t<<1|1].loop=tree[t].loop; if(tree[t].loop) { tree[t<<1].lsum=tree[t<<1].rsum=tree[t<<1].sum=0; tree[t<<1|1].lsum=tree[t<<1|1].rsum=tree[t<<1|1].sum=0; } else { tree[t<<1].lsum=tree[t<<1].rsum=tree[t<<1].sum=tree[t<<1].r-tree[t<<1].l+1; tree[t<<1|1].lsum=tree[t<<1|1].rsum=tree[t<<1|1].sum=tree[t<<1|1].r-tree[t<<1|1].l+1; } tree[t].loop=-1; } }
它就是与延时标记有关的 ,若该节点的 loop 值为 -1 就不需要执行,若为0,将左儿子的区间,右儿子的区间置空,若为1,则置满,分别对应改变对应lsum rsum sum loop 值,改变儿子的之后 注意要将 该节点 的 loop值变为 -1
四:如何判断 是否能找到连续长为x的房间
只需开始判断 tree[ 1 ].sum 和 x 的值即可……
…………………………………感觉说的已经很清晰了,再具体就参考代码吧……………………
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
#include<cmath>
#include<string>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
struct node{
ll l,r,lmx,rmx,mx,lazy;
}a[200000];
//更新函数
void update(ll k){
//求a[k].lmx
a[k].lmx=a[k<<1].lmx;
if(a[k].lmx>=(a[k].r+a[k].l)/2-a[k].l+1)
a[k].lmx+=a[k<<1|1].lmx;
//求a[k].rmx
a[k].rmx=a[k<<1|1].rmx;
if(a[k].rmx>=a[k].r-(a[k].r+a[k].l)/2)
a[k].rmx+=a[k<<1].rmx;
//求a[k].mx
a[k].mx=max(max(a[k<<1].mx,a[k<<1|1].mx),a[k<<1].rmx+a[k<<1|1].lmx);
}
//传递函数
void pushdown(ll k){
if(a[k].lazy==-1)return ;
//lazy传递
a[k<<1].lazy=a[k<<1|1].lazy=a[k].lazy;
//如果lazy是1;则空房连续的数目为0,反之为区间长度
if(a[k].lazy){
a[k<<1].lmx=a[k<<1].rmx=a[k<<1].mx=0;
a[k<<1|1].lmx=a[k<<1|1].rmx=a[k<<1|1].mx=0;
}
else{
a[k<<1].lmx=a[k<<1].rmx=a[k<<1].mx=a[k<<1].r-a[k<<1].l+1;
a[k<<1|1].lmx=a[k<<1|1].rmx=a[k<<1|1].mx=a[k<<1|1].r-a[k<<1|1].l+1;
}
a[k].lazy=-1;
}
//建线段树
void build(ll k,ll l,ll r){
a[k].l=l;a[k].r=r;a[k].lazy=-1;
//一开始全为空房
a[k].lmx=a[k].mx=a[k].rmx=a[k].r-a[k].l+1;
if(l==r){
return ;
}
ll mid=(l+r)/2;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
update(k);
}
//区间修改函数
void change(ll k,ll l,ll r,ll cnt){
if(a[k].l>=l&&a[k].r<=r){
a[k].lazy=cnt;
//如果要入宿,该区间内和为0
if(cnt){
a[k].lmx=a[k].mx=a[k].rmx=0;
}
else {
a[k].lmx=a[k].mx=a[k].rmx=a[k].r-a[k].l+1;
}
return ;
}
pushdown(k);
ll mid=(a[k].l+a[k].r)/2;
if(l<=mid){
change(k<<1,l,r,cnt);
}
if(r>mid){
change(k<<1|1,l,r,cnt);
}
update(k);
}
//寻找连续cnt个空房的区间的左端点,并且尽可能小
ll query(ll k,ll cnt){
if(a[k].l==a[k].r){
return a[k].l;
}
pushdown(k);
ll mid=(a[k].l+a[k].r)/2;
if(a[k<<1].mx>=cnt){
query(k<<1,cnt);
}
else if(a[k<<1].rmx+a[k<<1|1].lmx>=cnt){
return mid-a[k<<1].rmx+1;
}
else return query(k<<1|1,cnt);
}
int main(){
ll n,m;
while(~scanf("%lld %lld",&n,&m)){
build(1,1,n);
while(m--){
ll i,j,k;
scanf("%lld",&i);
if(i==1){
scanf("%lld",&j);
//先判断总的区间有没有比j长的连续区间
if(a[1].mx<j){
printf("0\n");
continue;
}
ll left=query(1,j);
printf("%lld\n",left);
//入宿j人
change(1,left,left+j-1,1);
}
else{
//从端点j开始,退房k人
scanf("%lld %lld",&j,&k);
change(1,j,j+k-1,0);
}
}
}
return 0;
}