BZOJ 3050: [Usaco2013 Jan] Seating

Description

To earn some extra money, the cows have opened a restaurant in their barn specializing in milkshakes. The restaurant has N seats (1 <= N <= 500,000) in a row. Initially, they are all empty. Throughout the day, there are M different events that happen in sequence at the restaurant (1 <= M <= 300,000). The two types of events that can happen are: 1. A party of size p arrives (1 <= p <= N). Bessie wants to seat the party in a contiguous block of p empty seats. If this is possible, she does so in the lowest position possible in the list of seats. If it is impossible, the party is turned away. 2. A range [a,b] is given (1 <= a <= b <= N), and everybody in that range of seats leaves. Please help Bessie count the total number of parties that are turned away over the course of the day.

m(m<=300,000)个操作。操作分2种:
1.A p,表示把编号最小的空着的长度为p的区间图上颜色。
2.L a b,表示把从a到b的区间(包括端点)全部擦干净(没颜色还是没颜色)。
Q:有多少个操作1不能实现?

Input

Line 1: Two space-separated integers, N and M.
Lines 2..M+1: Each line describes a single event. It is either a line of the form “A p” (meaning a party of size p arrives) or “L a b” (meaning that all cows in the range [a, b] leave).

题目大意就是给定一段长度为n的01序列,初始时全是0,然后有m个操作:
- A p 表示在序列内找到一段长度为p的全是0的区间并将其变成1,若有多个区间符合条件则修改靠前的区间,如果没有符合条件的区间则答案加一;
- L [l,r] 表示将区间[l,r]全部变为0;

本题要维护区间,修改区间值,那么就考虑线段树。修改比较简单,是线段树的模板操作,在所要修改的区间的根节点打标记即可;而判断是否有符合条件的区间,则需要一些附加操作。对于线段树的每一个节点,维护其子树中值为0的最长前缀长度,值为0的最长后缀长度,以及值为0的最长区间长度。那么查询时,判断根结点的最长连续区间长度是否大于p,若小于p则ans+1,否则就向下查询,如果左子树中的最长区间符合条件,就递归到左子树,如果左子树后缀加右子树前缀符合条件,则查询区间,并将其赋值为1,同时更新路径上的点;否则递归到右子树;

void insert(int now,int l,int r,int p) {
    if(p==0) return;
    int c,h;c=t[now].ch[0];h=t[now].ch[1];
    int mid=l+r>>1;
    if(c&&t[c].max_sum>=p) insert(c,l,mid,p);
    else if(t[c].sur+t[h].pre>=p){
      if(p==1) {
        t[now].full=1;
        markdown(now);markdown(c);markdown(h);
        return;
      }
      int a=mid-t[c].sur+1,b=mid+(p-t[c].sur);
      find(now,l,r,a,b,1);
    }
    else if(h&&t[h].max_sum>=p) insert(h,mid+1,r,p);
    update(now);
}

t[now]表示当前节点,t[now].max_sum表示该子树中的最长连续为0区间的长度;find(now,l,r,a,b,1)表示在区间为[l,r]的子树now中找区间[a,b]并将其赋值为1;
本题与另一道题很像 在我的另一篇博文里有更详细的讲解和更多操作的实现,是用Splay实现的。有兴趣的可以研究一下。

点这里 -> BZOJ 1500题解

下面是本题代码(程序有些慢,,大概7000毫秒左右。):

#include<iostream>
#include<cstdio>
using namespace std;
struct node { 
    int ch[2],v,full,max_sum,pre,sur,sum;
    node() { ch[0]=ch[1]=full=max_sum=sum=v=pre=sur=0; } 
}t[1500010];
int n,m,head,ans;
void markdown(int now) {
    if(t[now].full) {
      t[now].full=0; t[now].max_sum=t[now].pre=t[now].sur=0;
      if(t[now].ch[0]) t[now*2].full=1,t[now*2].v=0;
      if(t[now].ch[1]) t[now*2+1].full=1,t[now*2+1].v=0;
      t[now].pre=t[now].sur=t[now].max_sum=0;
    }
    if(t[now].v) {
      t[now].v=0; t[now].max_sum=t[now].pre=t[now].sur=t[now].sum;
      if(t[now].ch[0]) t[now*2].v=1,t[now*2].full=0;
      if(t[now].ch[1]) t[now*2+1].v=1,t[now*2+1].full=0;
    }
}
void update(int now) {
    markdown(now);
    if(!t[now].ch[0]&&!t[now].ch[1]) return;
    markdown(now*2); markdown(now*2+1);
    t[now].pre=t[now*2].pre; t[now].sur=t[now*2+1].sur;
    t[now].max_sum=max(t[now*2+1].max_sum,t[now*2].max_sum);
    if(t[now*2].pre==t[now*2].sum) 
      t[now].pre=t[now*2].pre+t[now*2+1].pre;
    if(t[now*2+1].sur==t[now*2+1].sum)
      t[now].sur=t[now*2+1].sur+t[now*2].sur;
    t[now].max_sum=max(t[now].max_sum,t[now*2].sur+t[now*2+1].pre);
}
void build(int now,int l,int r) {
    if(l==r) {
      t[now].sum=t[now].pre=t[now].sur=t[now].max_sum=1;return;
    }
    int mid=l+r>>1;
    t[now].ch[0]=now*2; t[now].ch[1]=now*2+1;
    build(now*2,l,mid); build(now*2+1,mid+1,r);
    t[now].sum=t[now*2].sum+t[now*2+1].sum;
    t[now].pre=t[now].sur=t[now].max_sum=t[now].sum;
}
void find(int now,int l,int r,int a,int b,int d) {
    markdown(now);
    if(l>=a&&r<=b) { 
      if(d) t[now].full=1;
      else t[now].v=1;
      markdown(now); return; 
    }
    int mid=l+r>>1;
    if(a<=mid) find(now*2,l,mid,a,b,d);
    if(b>mid) find(now*2+1,mid+1,r,a,b,d);
    update(now);
    return;
}
void insert(int now,int l,int r,int p) {
    if(p==0) return;
    int c,h;c=t[now].ch[0];h=t[now].ch[1];
    int mid=l+r>>1;
    if(c&&t[c].max_sum>=p) insert(c,l,mid,p);
    else if(t[c].sur+t[h].pre>=p){
      if(p==1) {
        t[now].full=1;
        markdown(now);markdown(c);markdown(h);
        return;
      }
      int a=mid-t[c].sur+1,b=mid+(p-t[c].sur);
      find(now,l,r,a,b,1);
    }
    else if(h&&t[h].max_sum>=p) insert(h,mid+1,r,p);
    update(now);
}
int in() {
    int s=0,v=0;char c;
    while((c=getchar())<'0'||c>'9') if(c=='-') v=1;s=c-'0';
    while((c=getchar())>='0'&&c<='9') s=s*10+c-'0';
    return v?-s:s;
}
int main() {
    n=in();m=in();
    head=1; build(head,1,n);
    for(int i=1;i<=m;i++) {
      char z;while((z=getchar())!='A'&&z!='L');
      if(z=='A') {
        int p=in(); if(t[head].max_sum>=p) insert(head,1,n,p); else ans++;
      }
      else {
        int a=in(),b=in();
        find(head,1,n,a,b,0);
      }
    } printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值