[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
    评论
好的,这是一道经典的单调栈问题。题目描述如下: 有 $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$ 的湖。如果栈不为空,说明找到了一个前驱湖,答案加一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值