【BZOJ3050】【USACO 2013 Jan Gold金组】坐座位 Seating

【USACO 2013 1月金组】seating

时间限制: 1 Sec 内存限制: 128 MB

题目描述

为了赚更多的钱,奶牛场开了一间专门做奶昔的餐馆。这个餐馆有N个位子(1<=N<=500000)排成一行,开始时,位子都是空的。
每天,有M个不同的事件按次序发生(1<=M<=300000).事件分为两类:
1.举办一个party,这个party有p头奶牛(1<=p<=N),这p头奶牛只会坐在相邻的位子。如果没有p个连续的空位,则奶牛们会离开。如果有多个,奶牛们会选择起点编号最小的一段空位。
2.区间[a,b]的奶牛们离开座位。(1<=a

输入

第一行:两个整数N,M。
第二行到第M+1行:每一行表示一个事件,它要么是形如“A p”,表示有一个party,这个party有p头奶牛;要么是形如L a b 的一行,表示区间[a,b]的所有奶牛全部离开。

输出

一行,表示不满足要求的聚会个数。

样例输入

10 4
A 6
L 2 4
A 5
A 2

样例输出

1

解题报告:

某天考试题。。。
比较裸的线段树题目,关键在于代码实现有点难(考试时没调出来)
用线段树控制区间的删除与增加,
每个线段树上节点,维护3个变量le,max,re。

struct Node{
    int l,r;
    int le,re,max;
    int lz;
}nodes[MAXN*5];

他们分别表示该区间内部靠左的空间大小,区间中不与左右空区间接触的最大空区间大小,该区间内部靠右的空区间大小。
每次updata操作这样写:

inline void updata(int u){
    nodes[u].max=maxf(nodes[u<<1].max,nodes[(u<<1)+1].max);
    nodes[u].max=maxf(nodes[u].max,nodes[u<<1].re+nodes[(u<<1)+1].le);
    le[u]=nodes[u<<1].le;
    re[u]=nodes[(u<<1)+1].re;
    if(nodes[u<<1].le==nodes[u<<1].r-nodes[u<<1].l+1)
        le[u]+=nodes[(u<<1)+1].le;
    if(nodes[(u<<1)+1].re==nodes[(u<<1)+1].r-nodes[(u<<1)+1].l+1)
        re[u]+=nodes[u<<1].re;
}

lazy标记保存三个值,非别是0,1,2
代表无操作,填满和清空

const int REMOVE=2;
const int FILL=1;

下传lazy时这么写:

inline void pushdown(int index){
    nodes[index<<1].lz=nodes[(index<<1)+1].lz=nodes[index].lz;
    if(nodes[index].lz==FILL){
        nodes[index<<1].le=nodes[index<<1].re=nodes[index<<1].max=0;
        nodes[(index<<1)+1].le=nodes[(index<<1)+1].re=nodes[(index<<1)+1].max=0;
    }
    else if(nodes[index].lz==REMOVE){
        nodes[index<<1].max=nodes[index<<1].re=nodes[index<<1].le
        =nodes[index<<1].r-nodes[index<<1].l+1;
        nodes[(index<<1)+1].max=nodes[(index<<1)+1].re=nodes[(index<<1)+1].le
        =nodes[(index<<1)+1].r-nodes[(index<<1)+1].l+1;
    }
    nodes[index].lz=0;
}

由于每次A操作要寻找一段最靠左长度为len的空区间
因此多添加一个函数:

int len;
int query(int u){
    if(nodes[u].l==nodes[u].r)return nodes[u].l;
    if(nodes[u].lz)
        pushdown(u);
    if(nodes[u<<1].max>=len)return query(u<<1);
    else if(nodes[u<<1].re+nodes[(u<<1)+1].le>=len)
        return nodes[u<<1].r-nodes[u<<1].re+1;
    else return query((u<<1)+1);
}

此函数用于寻找长度为len的空区间左端点位置
原理:在保持当前区间中最大空区间长度大于len时尽量向左儿子前进,最后找到的一定是最靠左的区间。

下面发AC代码:

#include<cstdio>
const int MAXN=510000;
const int MAXM=310000;
const int REMOVE=2;
const int FILL=1;
inline int max(const int &a,const int &b)
{return a<b?b:a;}
inline int min(const int &a,const int &b)
{return a<b?a:b;}
inline void getint(int &t){
    register char c;t=0;
    do{c=getchar();}while(c<'0'||c>'9');
    while(c<='9'&&c>='0'){t=t*10+c-'0';c=getchar();}
}
struct Node{
    int l,r;
    int le,re,max;
    int lz;
}nodes[MAXN*5];
inline void pushdown(int index){
    nodes[index<<1].lz=nodes[(index<<1)+1].lz=nodes[index].lz;
    if(nodes[index].lz==FILL){
        nodes[index<<1].le=nodes[index<<1].re=nodes[index<<1].max=0;
        nodes[(index<<1)+1].le=nodes[(index<<1)+1].re=nodes[(index<<1)+1].max=0;
    }
    else if(nodes[index].lz==REMOVE){
        nodes[index<<1].max=nodes[index<<1].re=nodes[index<<1].le
        =nodes[index<<1].r-nodes[index<<1].l+1;
        nodes[(index<<1)+1].max=nodes[(index<<1)+1].re=nodes[(index<<1)+1].le
        =nodes[(index<<1)+1].r-nodes[(index<<1)+1].l+1;
    }
    nodes[index].lz=0;
}
inline void updata(int u){
    nodes[u].max=max(nodes[u<<1].max,nodes[(u<<1)+1].max);
           nodes[u].max=max(nodes[u].max,nodes[u<<1].re+nodes[(u<<1)+1].le);
    nodes[u].le=nodes[u<<1].le;
    nodes[u].re=nodes[(u<<1)+1].re;
    if(nodes[u<<1].le==nodes[u<<1].r-nodes[u<<1].l+1)
        nodes[u].le+=nodes[(u<<1)+1].le;
    if(nodes[(u<<1)+1].re==nodes[(u<<1)+1].r-nodes[(u<<1)+1].l+1)
        nodes[u].re+=nodes[u<<1].re;
}
void build(int u,int l,int r){
    nodes[u].l=l;
    nodes[u].r=r;
    nodes[u].le=nodes[u].re=nodes[u].max=r-l+1;
    if(l==r)return ;
    int mid=(l+r)>>1;
    build(u<<1,l,mid);
    build((u<<1)+1,mid+1,r);
}
int l,r,k;
void fill(int u){
    if(nodes[u].r<l||nodes[u].l>r)return ;
    if(nodes[u].lz)
        pushdown(u);
    if(nodes[u].l>=l&&nodes[u].r<=r){
        nodes[u].lz=k;
        if(k==REMOVE)nodes[u].le=nodes[u].re=nodes[u].max=nodes[u].r-nodes[u].l+1;
        else nodes[u].le=nodes[u].re=nodes[u].max=0;
        return ;
    }
    fill(u<<1);
    fill((u<<1)+1);
    updata(u);
}
int len;
int query(int u){
    if(nodes[u].l==nodes[u].r)return nodes[u].l;
    if(nodes[u].lz)
        pushdown(u);
    if(nodes[u<<1].max>=len)return query(u<<1);
    else if(nodes[u<<1].re+nodes[(u<<1)+1].le>=len)
        return nodes[u<<1].r-nodes[u<<1].re+1;
    else return query((u<<1)+1);
}
int main(){
    int n,m;
    getint(n),getint(m);
    build(1,1,n);
    int ans=0;
    register char o;
    register int a,b,c;
    for(int i=1;i<=m;i++){
        while((o=getchar())!='L'&&o!='A');
        if(o=='L'){
            getint(a);
            getint(b);
            l=a;
            r=b;
            k=REMOVE;
            fill(1);
        }else {
            getint(c);
            if(c>nodes[1].max){ans++;continue;}
            len=c;
            int pos=query(1);
            l=pos;
            r=pos+c-1;
            k=FILL;
            fill(1);
        }
    }
    printf("%d\n",ans);
}
//ORZ GJY LJH  TSY SYK YLY GMR YMX

此题在bzoj上每个点给了10秒的时限,但是在学校oj上只给一秒。。。
于是就成了卡常神题 = =

我从两天前开始就是一阵狂T,把能想到的inline,读入优化,等等全部写上,再把函数参数全改成全局变量,
结果是这样的:
哎
= =
心酸呐= =
然后只好在群里求助大神,大神说加个注释就能保证卡过!
于是加上:

//ORZ GJY LJH  TSY SYK YLY GMR YMX

然后果然交了两发就过了啊哈哈哈

===============================分割线===============================
PS
据说这道题有华丽丽的常数超小不会被卡的差分做法

好的,这是一道经典的单调栈问题。题目描述如下: 有 $n$ 个湖,第 $i$ 个湖有一个高度 $h_i$。现在要在这些湖之间挖一些沟渠,使得相邻的湖之间的高度差不超过 $d$。请问最少需要挖多少个沟渠。 这是一道单调栈的典型应用题。我们可以从左到右遍历湖的高度,同时使用一个单调栈来维护之前所有湖的高度。具体来说,我们维护一个单调递增的栈,栈中存储的是湖的下标。假设当前遍历到第 $i$ 个湖,我们需要在之前的湖中找到一个高度最接近 $h_i$ 且高度不超过 $h_i-d$ 的湖,然后从这个湖到第 $i$ 个湖之间挖一条沟渠。具体的实现可以参考下面的代码: ```c++ #include <cstdio> #include <stack> using namespace std; const int N = 100010; int n, d; int h[N]; stack<int> stk; int main() { scanf("%d%d", &n, &d); for (int i = 1; i <= n; i++) scanf("%d", &h[i]); int ans = 0; for (int i = 1; i <= n; i++) { while (!stk.empty() && h[stk.top()] <= h[i] - d) stk.pop(); if (!stk.empty()) ans++; stk.push(i); } printf("%d\n", ans); return 0; } ``` 这里的关键在于,当我们遍历到第 $i$ 个湖时,所有比 $h_i-d$ 小的湖都可以被舍弃,因为它们不可能成为第 $i$ 个湖的前驱。因此,我们可以不断地从栈顶弹出比 $h_i-d$ 小的湖,直到栈顶的湖高度大于 $h_i-d$,然后将 $i$ 入栈。这样,栈中存储的就是当前 $h_i$ 左边所有高度不超过 $h_i-d$ 的湖,栈顶元素就是最靠近 $h_i$ 且高度不超过 $h_i-d$ 的湖。如果栈不为空,说明找到了一个前驱湖,答案加一。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值