洛谷P3522/POI2011 TEM-Temperature

发布时间 2023-11-01 17:15:29作者: SkyNet-PKN

涉及知识点:单调队列、贪心、递推

前言

最近找了点单调队列的题练练手,就遇到这道题,本题对于单调队列弹队尾的时候有别于普通单调队列,一些题解并没有写的很清楚,因此写下这篇题解。

题面

Link

你有一个长度为 \(n\) 的序列 \(A\),告诉你序列中每一个数 \(A_i\) 的取值范围 \(down_i\) ~ \(up_i\),请你求出最大的可能的非降子段长度(注意是子段非子序列,要连续)。

思路

我们首先不思考如何保证一段区间都是非降的,先考虑两个数是非降的(即 \(Ai\leq A_j\))满足什么条件,简单枚举两个数的取值范围所有可能的相对关系,发现 \(down_i\leq up_j\) 即有可能满足 \(A_i\leq A_j\),否则一定不满足。

于是我们考虑到,一段区间是非降的,等价于这段区间两两元素都是非降的,即对于区间内所有的 \(i<j\) 都满足 \(A_i\leq A_j\);根据上文的结论,进一步转化为,对于区间中所有的 \(i<j\) 都满足 \(down_i\leq up_j\);当然还可以进一步简化为,对于区间 \([l,r]\) 内所有的 \(i\),都满足 \(\max\limits_{l\leq j<i}\{down_j\}\leq up_i\)。这样,我们就得到了一个在取值范围意义下的“连续非降段”的定义了,只要满足这个定义,这段就一定存在一种取值方案使得它是非降的。

由于刚才得到的重要结论 \(\max\limits_{l\leq j<i}\{down_j\}\leq up_i\),观察这个式子发现它的左边是一个前缀 \(\max\) 的形式,而右边又是一个单独的点,就想到是否可以从左到又依次加入每个点,对于新加入的点 \(i\),求出使上式成立的最小的 \(l\),然后更新答案。

考虑使用单调队列,维护一个 \(\max\limits_{l\leq j<i}\{down_j\}\) 单调递减的队列。

具体来说分为以下几步,设当前遍历到 \(i\)

  1. 弹出不合法的队头:如果队头有状态不满足 \(\max\limits_{l\leq j<i}\{down_j\}\leq up_i\),那么就可以弹出了。因为如果 \(\max\limits_{l\leq j<i}\{down_j\}\leq up_i\) 不成立则说明一定存在至少一个 \(j\) 使得 \(down_j>up_i\)。根据上文,这种情况意味着 \(A_j\leq A_i\) 一定不满足,因此 \(i\) 加入后该子段就不满足非降了。又因为题目要求子段是连续的,所以这个状态之后一定没用了,可以弹出。
  2. 更新答案:弹出了不合法的堆头后,根据单调性,由于 \(l\) 小的一定比 \(l\) 大的先入队,所以此时的队头一定是 \(l\) 最小的合法状态了,该状态是最优的,以队头的 \(l\) 作为子段左端点,当前遍历到的 \(i\) 作为子段右端点来更新答案。
  3. 挤掉不优的队尾&插入当前状态:对于队列里那些 \(\max\limits_{l\leq j<i}\{down_j\}\leq down_i\)(注意这里变成 \(down_i\) 了)的状态,以及以当前遍历到的 \(i\) 作为区间起点 \(l\) 的状态,这些状态在 \(i\) 加入后 \(\max\limits_{l\leq j<i}\{down_j\}\) 肯定都会变为 \(down_i\),因此我们不如将这些状态合并为一个,取其中最小的 \(l\) 作为新状态的 \(l\),最后插入队尾。这个操作也相当于弹掉了 \(\max\limits_{l\leq j<i}\{down_j\}\) 相同,\(l\) 不优的状态。

代码

Tips:

  1. ans初值需设为 \(1\),因为长度为 \(1\) 的子段也是非降子段
  2. 在弹出队尾,寻找最小的 \(l\) 时,由于 \(l\) 小的一定比 \(l\) 大的先插入,所以直接取最后一个弹出的 \(l\) 即可,不用取 \(\min\)
  3. 在插入整合后的状态时,直接 t[tmp].first=t[i].first 赋值是没有问题的,因为 tmp 原指的状态已经被弹出不再被使用,所以为节约空间可以直接覆盖
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
typedef pair<int,int> pii;
int n,ans=1;
pii t[MAXN];
deque<int>q;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>t[i].first>>t[i].second;
    }
    for(int i=1;i<=n;i++){
        while((!q.empty()) && (t[q.front()].first>t[i].second)) q.pop_front();
        if(!q.empty()){
            // cout<<q.front()<<' '<<i<<endl;
            ans=max(ans,i-q.front()+1);
        }
        int tmp=i;
        while((!q.empty()) && (t[q.back()].first<=t[i].first)){
            tmp=q.back();
            q.pop_back();
        }
        q.push_back(tmp);
        t[tmp].first=t[i].first;
    }
    cout<<ans<<endl;
    return 0;
}