[BZOJ2276][Poi2011]Temperature(单调队列)

题目描述

传送门

题解

本来简简单单的一道题让我写得极其愚蠢。

正常的题解是长这样的:
考虑若干个 l – r 区间,如果这几个区间的温度可能不降,min { l } 是可以被取到的,那么只要维护关于l的单调递减队列,保证队头的 l 小等于队尾的 r

我的做法:
首先用两个指针,左指针不动,右指针向后移直到不能走为止,统计答案,然后再把左指针向后移。这样的话每一个点最多被左右指针各访问一次,时间复杂度是 O(n) 的。
问题就在于怎样判断合法。一个贪心的思想是,每一个区间内的点应该尽量向下放。可是这样的话当左指针向前移动时也就是相当于删去了一个点,这时候所有的点有可能同时向下移动一个长度,得到的答案还是合法的,并且为右指针向后移创造了更优的条件。
考虑怎样动态维护。显然移动的长度一定是当前所有在范围内的点离它最小值的限制最近的那一段距离。那么我们可以维护这一段距离的值。设lazy标记为所有的点应该向下平移的量。每一次向下平移都要在lazy标记上加上平移的量。相应的,每一个点的坐标都应变为l+lazy和r+lazy,每一个点的实际高度和l的差值也应该为cha+lazy。也就是说,lazy是一个绝对值,为所有的点向下平移的量。但是端点坐标和差值都是一个相对值,点与点之间的关系不会改变,但是实际的数值是有可能变化的。
我们需要每一次 O(1) 地求所有在范围内的点的差的最小值,可以用单调队列来维护。每一个点只会进出队列一次,时间复杂度也是 O(n) 的。

不得不说比起标算来说我的做法确实愚蠢无比,但是我感觉这个延迟标记的思路是非常好的。处理绝对值和相对值的时候也会有一些问题。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 1000005
#define LL long long

int n,l,r,head,tail,ans;
struct hp{LL l,r;}deg[N];
LL cha[N],Min,lazy;
int q[N];

void push(int id)
{
    while (head<tail&&cha[q[tail]]>=cha[id]) --tail;
    q[++tail]=id;
}
LL pop()
{
    while (head<tail&&q[head+1]<l) ++head;
    return cha[q[head+1]]-lazy;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i) scanf("%lld%lld",&deg[i].l,&deg[i].r);
    l=r=1;Min=deg[1].l;head=tail=0,q[++tail]=1;
    while (l<=n)
    {
        if (l>r)
        {
            r=l,Min=deg[r].l,lazy=0;
        }
        while (r<n&&deg[r+1].r+lazy>=Min)
        {
            ++r;
            Min=max(Min,deg[r].l+lazy);
            cha[r]=Min-deg[r].l;
            push(r);
        }
        ans=max(ans,r-l+1);
        ++l;
        lazy+=pop();
    }
    printf("%d\n",ans);
}

总结

①绝对值和相对值的关系一定要搞清。老是出错的时候一定要把每一个变量的定义想一想 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值