参考了给老师的代码

题意:

给出n,m(1<=N,M<=50000),表示一个有N个空位的数列,对其进行M次操作。有这两种操作:
1 Di:表示将数列中最左端的连续Di个空位填满,输出被填满区间左端点的位置,若无法做到则不填并输出0。
2 Xi Di:将区间[Xi,Xi+Di-1]清空。

例:如果输入1 3,则:
poj 3667 Hotel (线段树+染色进阶)
输出:1
再输入1 2,则:
poj 3667 Hotel (线段树+染色进阶)
再输入2 2 3,则清空[2,2+3-1]的部分:
poj 3667 Hotel (线段树+染色进阶)

问题模型:

核心:维护一个区间的最长连续1(这个1代表的是空位)

那为了维护一个区间的最长连续1,就需要两个额外信息,从左端点开始的最长连续1和从右端点开始的最长连续1!这样在区间合并的时候,才能获得新区间真正的最长连续1。
举个例子:[1,5] 和[6,10]要合并,如果你只知道这个区间的最长连续1,你能合并吗?所以我们要构造如下的线段树结构,其中

我们有有三种操作:

  • 1.将[a, b]中的所有数字改成0
  • 2.将[a, b]中的所有数字改成1
  • 3.询问[a, b]中最长连续的1的长度是多少

前两种修改区间的操作有三种情况:

  • 1.放在左儿子那个区间。
  • 2.放在右儿子那个区间。
  • 3.放在左右儿子中间,就是占用左区间的右部分和右区间的左部分。

要求查询时线段树要能直接获取区间的最大连续长度,需要维护四个值,分别是:

  • lsum记录该区间左端点开始的最长连续的值为1区间,用来表示左区间最长连续空房数
  • rsum记录该区间右端点开始的最长连续的值为1区间,用来表示右区间最长连续空房数
  • sum记录该区间内最长连续的值为1的区间,用来表示用区间最长连续空房数
  • cover形象解释就是记录区间的“颜色”,具体操作是当这个区间全部是 1时color置1,全部为0时color置0,否则置-1。在pushup()的时候会用到。
向下更新策略:

先向下转移标记cover,在通过标记更新相应的数据。

向上更新策略:
  • sum=max(左儿子的sum,右儿子的sum,左右儿子中间部分)
  • lsum=左儿子的lsum,如果左儿子为空还要加上右儿子的lsum(所谓的左儿子为空事实上等于左儿子等于区间长度)
  • rsum=右儿子的rsum,如果右儿子为空还要加上左儿子的rsum
    poj 3667 Hotel (线段树+染色进阶)

思路:

  • 利用线段树建立模型,维护最大连续区间长度,其中区间长度就是对应的房间数目,并且对应区间中最左边的断点就是answer。
  • 同时因为需要求出连续区间的最大长度,因此每次PushUp时都将左右区间合并,lsum维护左区间的最大长度,rsum维护右区间的最大长度,msum维护区间1…N中的最大连续区间长度。
  • cover标志对应区间是否为空(没有住客)。cover表示区间状态如下:
    0:区间全空。(区间里面全都是1,这个太坑爹了),1:区间全满。(区间全都是0),-1:当前区间已初始化,不用向下更新。
#include<iostream>
#include<string.h>
#include<algorithm>
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
const int maxn = 55555<<1;
struct Hotel
{
   int lsum;//最长左1
   int rsum;//最长右1
   int sum;//最长连续1
   int cover;
} hotel[maxn<<2];
void pushUp(int rt,int m)//因为区间赋值需要区间长度作为参数,所以这里需要一个m
{
   hotel[rt].lsum = hotel[ls].lsum;
   hotel[rt].rsum = hotel[rs].rsum;

   if(hotel[rt].lsum==m-(m>>1))//所谓的左儿子为空事实上是做儿子等于区间长度,即区间内部全都是1
       hotel[rt].lsum+=hotel[rs].lsum;
   if(hotel[rt].rsum==(m>>1))//所谓的左儿子为空事实上是做儿子等于区间长度
       hotel[rt].rsum+=hotel[ls].rsum;

   hotel[rt].sum = max(hotel[ls].rsum+hotel[rs].lsum,max(hotel[ls].sum,hotel[rs].sum));
}
void pushDown(int rt,int m)
{
   if(hotel[rt].cover!=-1)
   {
       hotel[rs].cover = hotel[ls].cover = hotel[rt].cover;//将cover向下推
       hotel[ls].sum = hotel[ls].lsum = hotel[ls].rsum = hotel[rt].cover?0:m-(m>>1);//得到左边的和,要不为空,要不就是m-m/2
       hotel[rs].sum = hotel[rs].lsum = hotel[rs].rsum = hotel[rt].cover?0:(m>>1);//得到右边的和
       hotel[rt].cover = -1;
   }
}
void build(int l,int r,int rt)
{
   hotel[rt].lsum = hotel[rt].rsum = hotel[rt].sum = r-l+1;
   hotel[rt].cover = -1;//当前空间已初始化,不用向下更新
   if(l==r)
       return ;
   int m = (l+r)>>1;
   build(l,m,ls);
   build(m+1,r,rs);
}
void update(int L,int R,int C,int l,int r,int rt)
{
   if(L<=l&&r<=R)
   {
       hotel[rt].cover = C;
       hotel[rt].sum = hotel[rt].rsum = hotel[rt].lsum = hotel[rt].cover?0:r-l+1;
       return ;
   }
   int m = (r+l)>>1;
   pushDown(rt,r-l+1);
   if(L<=m)
       update(L,R,C,l,m,ls);
   if(R>m)
       update(L,R,C,m+1,r,rs);
   pushUp(rt,r-l+1);
}
int query(int w,int l,int r,int rt)//w代表要删除的数据?
{
   if(l==r)
       return l;
   pushDown(rt,r-l+1);
   int m = (l+r)>>1;
   if(hotel[ls].sum>=w)
       return query(w,l,m,ls);//.sum代表空房数量,如果空房数量足够,优先往靠前的走
   else if(hotel[ls].rsum+hotel[rs].lsum>=w)
       return m-hotel[ls].rsum+1;//有可能空房部分在中间
   return query(w,m+1,r,rs);
}
int main()
{
   //freopen("poj 3667.txt","r",stdin);
   int n,k;
   while(scanf("%d%d",&n,&k)!=EOF)
   {
       build(1,n,1);
       while(k--)
       {
           //for(int i = 1;i<=17;++i)
           //printf("%d ",hotel[i].sum);
           //printf("\n");
           int op,a,b;
           scanf("%d%d",&op,&a);
           if(op==1)
           {
               if(hotel[1].sum<a)
               {
                   printf("0\n");
                   continue;
               }
               int p = query(a,1,n,1);
               printf("%d\n",p);
               update(p,p+a-1,1,1,n,1);
           }
           else
           {
               scanf("%d",&b);
               update(a,a+b-1,0,1,n,1);//输入的5 5代表清除5~9
           }
       }
   }
   return 0;
}

相关文章: