先附上大佬博客Orz:https://blog.csdn.net/dan__ge/article/details/51746590
http://www.itkeyword.com/doc/7101227000454544193/hdu3530-Subsequence
找数列中最长的子序列,要求最大值减去最小值大于等于m小于等于k。这道题被归到了单调队列里,但真的没想到要用两个。。之前做过几道单调队列的题,实现原理大概就是维护队首并在队尾插入元素,保证队列的单调性。队首看题目要求,比如限制区间长度,本题在后边分析;而队尾每插入一个元素就要从后往前去除冗杂状态。
设head和tail为维护区间的左右端点,构造两个单调队列:一个递增序列up[]和一个递减序列down[],它们都以tail为结尾,且队首元素的下标>head;这两个序列存的是元素值,所以另外再开两个数组记录元素值对应的下标(其实直接记录下标也可以)。tail从1到n遍历,每次都插入up和down队列末尾。难点在于队首的维护。可知在当前区间中,最大值是down的队首元素,最小值是up的队首元素,若max-min>k则需要令head++(使max减小或min增大);若head超过了up或down的队首元素下标,更新两数组的队首(其实就是队首指针++)。直到max-min<k,判断max-min>=m是否满足,满足则更新答案。附上AC代码如下:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const int MAX=100005;
int n,m,k;
int a[MAX];
//递增序列
int up[MAX],id1[MAX];//记录数值,下标
int h1,t1;//队首,队尾
//递减序列
int down[MAX],id2[MAX];
int h2,t2;
void init()
{
memset(up,0,sizeof(up));memset(id1,0,sizeof(id1));
memset(down,0,sizeof(down));memset(id2,0,sizeof(id2));
h1=1;t1=0;h2=1;t2=0;//注意此处初始化(与后边的"h1<=t1"对应)!
}
int main()
{
while(scanf("%d%d%d",&n,&m,&k)==3)
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
init();int ans=0;
int head=1;
for(int tail=1;tail<=n;tail++)
{
//队尾插入元素并去掉影响单调性的值
while(h1<=t1&&up[t1]>a[tail])//维护递增
t1--;
up[++t1]=a[tail];id1[t1]=tail;//记录下标
while(h2<=t2&&down[t2]<a[tail])//维护递减
t2--;
down[++t2]=a[tail];id2[t2]=tail;//记录下标
//维护队首,满足m<=max-min<=k
while(down[h2]-up[h1]>k&&h1<=t1&&h2<=t2)
{
if(head==id1[h1])//head超过了队首元素下标
h1++;
if(head==id2[h2])
h2++;
head++;
}
if(down[h2]-up[h1]>=m)
ans=max(ans,tail-head+1);//更新结果
}
printf("%d\n",ans);
}
return 0;
}