[BZOJ1593][Usaco2008 Feb]Hotel 旅馆(线段树)

题目描述

传送门

题解

十倍经验题= =
线段树的一个结点维护3个值,分别是区间最长,左端点连续最长,右端点连续最长,然后就可以搞了。
update的时候注意分几种情况,还有整个区间都被覆盖的特殊情况。
最后查询开头的时候需要一点小技巧。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int max_n=5e4+5;
const int max_tree=max_n*4;

struct hp{
    int val,lval,rval;
    int l,r,len;
};
hp tree[max_tree];
int delta[max_tree];
int n,m,len,lrange,rrange,opt;
int ans;

inline void update(int now){
    if (tree[now<<1].lval==tree[now<<1].len)
      tree[now].lval=tree[now<<1].lval+tree[now<<1|1].lval;
    else
      tree[now].lval=tree[now<<1].lval;

    if (tree[now<<1|1].rval==tree[now<<1|1].len)
      tree[now].rval=tree[now<<1|1].rval+tree[now<<1].rval;
    else
      tree[now].rval=tree[now<<1|1].rval;

    int midval=tree[now<<1].rval+tree[now<<1|1].lval;
    tree[now].val=max(midval,max(tree[now<<1].val,tree[now<<1|1].val));
}

inline void pushdown(int now,int l,int r,int mid){
    if (delta[now]>=0){
        if (delta[now]==0){
            tree[now<<1].val = tree[now<<1].len;
            tree[now<<1].lval = tree[now<<1].len;
            tree[now<<1].rval = tree[now<<1].len;
            delta[now<<1] = delta[now];

            tree[now<<1|1].val = tree[now<<1|1].len;
            tree[now<<1|1].lval = tree[now<<1|1].len;
            tree[now<<1|1].rval = tree[now<<1|1].len;
            delta[now<<1|1] = delta[now];
        }
        else{
            tree[now<<1].val = 0;
            tree[now<<1].lval = 0;
            tree[now<<1].rval = 0;
            delta[now<<1] = delta[now];

            tree[now<<1|1].val = 0;
            tree[now<<1|1].lval = 0;
            tree[now<<1|1].rval = 0;
            delta[now<<1|1] = delta[now];
        }
        delta[now]=-1;
    }
}

inline void build(int now,int l,int r){
    int mid=(l+r)>>1;

    tree[now].l=l; tree[now].r=r;
    tree[now].len=r-l+1;
    delta[now]=-1;

    if (l==r){
        tree[now].val = tree[now].len;
        tree[now].lval = tree[now].len;
        tree[now].rval = tree[now].len;
        return;
    }

    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);

    update(now);
}

inline void interval_change(int now,int l,int r,int lrange,int rrange,int v){
    int mid=(l+r)>>1;
    if (lrange<=l&&r<=rrange){
        delta[now]=v;
        if (v==0){
            tree[now].val = tree[now].len;
            tree[now].lval = tree[now].len;
            tree[now].rval = tree[now].len;
        }
        else{
            tree[now].val = 0;
            tree[now].lval = 0;
            tree[now].rval = 0;
        }
        return;
    }
    pushdown(now,l,r,mid);
    if (lrange<=mid)
      interval_change(now<<1,l,mid,lrange,rrange,v);
    if (mid+1<=rrange)
      interval_change(now<<1|1,mid+1,r,lrange,rrange,v);
    update(now);
}

inline int find(int now,int l,int r,int len){
    int mid=(l+r)>>1;
    if (l==r) return l;
    pushdown(now,l,r,mid);
    if (tree[now<<1].val>=len)
      return find(now<<1,l,mid,len);
    else
      if (tree[now<<1].rval+tree[now<<1|1].lval>=len)
        return mid-tree[now<<1].rval+1;
    else
      return find(now<<1|1,mid+1,r,len);
}

int main(){
    scanf("%d%d",&n,&m);

    build(1,1,n);

    for (int i=1;i<=m;++i){
        scanf("%d",&opt);
        if (opt==1){
            scanf("%d",&len);
            if (tree[1].val>=len){
                ans=find(1,1,n,len);
                printf("%d\n",ans);
                lrange=ans;
                rrange=ans+len-1;
                interval_change(1,1,n,lrange,rrange,1);
            }
            else
              printf("0\n");
        }
        else{
            scanf("%d%d",&lrange,&len);
            rrange=lrange+len-1;
            interval_change(1,1,n,lrange,rrange,0);
        }
    }
}

总结

①如此傻逼的一道题竟然让我写成了140+,心里很不爽。最近写数据结构似乎非常冗长,调试慢,手残错误多。以后应该注意尽量避免。
②刚开始的思路错了,把区间最长的开头记了。其实是不对的,因为有可能有的区间不是最长的,但是也满足要求,同时起点靠前。
③后来又发现问题:update。呵呵傻逼了。

后话

Loli的测试碰巧考到了这道题,结果我因为奇怪的崩溃被卡成了70分。也不能说是奇怪,确实是有自己傻逼的地方。
惨痛的教训告诉我:
1、一提到线段树,一定要警醒三点:update、pushdown、递归到底层。所有的操作之前都要pushdown,所有的修改之后都要update,每个函数都要考虑递归到最底层的情况。
2、开Warning

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值