[hdu3530]Subsequence 解题报告

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Engels_Si_Fish/article/details/102743992

题目大意:

给出一串数字,求出最大值减去最小值在[m,k]范围内的最长子串长度。(注意:有多组数据)

原始思路:

按题意模拟,然而n的范围是[1,100000],必定爆炸,所以要另辟蹊径。然后不太会——于是上网搜了一发题解。感谢这几位dalao的博客。

(其实做法都一样吧)


分析:

单调队列概念:

顾名思义,单调队列的重点分为 “单调” 和 “队列”

“单调”指的是元素的“规律”——递增(或递减)

“队列”指的是元素只能从队头和队尾进行操作(等同于stl里的deque

所以说顺便得补习一下单调队列(一篇好博客)这个东西。

这题得用两个单调队列,一个维护以当前位置结束的单调上升子序列的位置,一个维护以当前位置结束的单调下降子序列的位置。单升的队头表示i之前最小值的位置,单降的队头表示i之前最大值的位置。之后不断作差比较并更新答案即可。

举个例子,大概是这样(?)

好吧其实我也不是特别会= =我谢罪orz

#pragma GCC optimize(2)
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 100005;
int n,m,k,a[N],qmin[N],qmax[N];
//qmin递减,qmax递增
inline int qread(){//快读
    int x=0,w=1;
    char ch=0;
    while(ch<'0'||ch>'9'){
        if(ch=='-')w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*w;
}

int main(){
    while(scanf("%d%d%d",&n,&m,&k)!=EOF){//多组数据
        for(int i=1;i<=n;++i)a[i]=qread();
        int s1=0,e1=0,s2=0,e2=0,ans=0,pos=0;//s1,e1为qmin头尾指针,s2,e2为qmax头尾指针,ans存答案,pos标记最长序列的位置
        for(int i=1;i<=n;++i){
            while(s1<e1&&a[qmin[e1-1]]<a[i])e1--;//维护单降队列(最大值)
            while(s2<e2&&a[qmax[e2-1]]>a[i])e2--;//维护单升队列(最小值)
            qmax[e2++]=qmin[e1++]=i;//将当前位置插入队尾
            while(s1<e1&&s2<e2&&a[qmin[s1]]-a[qmax[s2]]>k){//如果极差大于k则需要更新位置更小的那个队列以保证子序列最长
                if(qmin[s1]<qmax[s2])pos=qmin[s1++];//如果单降队列队首位置小于单升队首则让pos变为单降队首,队首再跳到下一位以便继续查找最优解
                else pos=qmax[s2++];//否则变为单升队首
            }
            if(s1<e1&&s2<e2&&a[qmin[s1]]-a[qmax[s2]]>=m)ans=max(ans,i-pos);//如果极差还满足小于m则可以更新答案
        }printf("%d\n",ans);//将所有点扫完后就能输出了
    }return 0;
}

(似乎还有 r m q rmq 和线段树这两种做法)

F I N FIN

猜你喜欢

转载自blog.csdn.net/Engels_Si_Fish/article/details/102743992