先附上大佬博客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;
}