原题地址:
http://acm.hdu.edu.cn/showproblem.php?pid=3530
题意:求最长子序列,使序列中最大值和最小值的差不小于m不超过n。
思路:
①head和tail分别表示当前所维护的区间首位;
②维护两个单调序列,序列内需要保存的是数的位置,单调递增(似乎这道题严格不严格都可以)队列up[i],其队首为从head到tail的最小值,单调递减队列down[i],其队首为从head到tail的最小值。
③从1开始枚举tail,每次tail进入区间时,首先更新两个单调序列,此时可更新从head到tail的最大最小值。
④如果max-min>k,则说明需要更新head,使max减小或min增大,使head++,如果出现haed越过当前的最大值或者最小值,则更新最大值或最小值,直到max-min<k。
⑤此时,如果max-min>=m的条件也满足的话,则可更新答案tail-head+1。
注意容易出错:
维护单调序列的时候,应该是以已经更新的队首为首,一开始写成了总是以0为首。
感想:昨晚用贪心写了一遍,写了100多行太乱了各种错误,晚上又睡着思考了一番,思路一下子就清晰了。单调队列这种东西真是需要多练练啊。
代码:
#include "stdio.h"
#include "string.h"
#include "iostream"
using namespace std;
int up[100010],hdown,hup,down[100010],nup,ndown,ans,num[100010];
int n,m,k;
void get_up(int i) //更新单调递增区间
{
while(1)
{
if(num[i]>num[up[nup]]||nup<hup)
{
up[++nup]=i;
break;
}
else
nup--;
}
}
void get_down(int i) //更新单调递减区间
{
while(1)
{
if(num[i]<num[down[ndown]]||ndown<hdown)
{
down[++ndown]=i;
break;
}
else
ndown--;
}
}
void init() //初始化以及输入
{
up[0]=0;
down[0]=0;
nup=0;
hup=1;
hdown=1;
ndown=0;
ans=0;
for(int i=1;i<=n;i++)
scanf("%d",&num[i]);
}
int main()
{
while(~scanf("%d%d%d",&n,&m,&k))
{
init();
int head=1;
for(int tail=1;tail<=n;tail++)
{
get_up(tail);
get_down(tail);
while(num[down[hdown]]-num[up[hup]]>k)
{
if(head==up[hup]) //head更新到最大值或者最小值,需要改变最大或最小值
hup++;
if(head==down[hdown])
hdown++;
head++;
}
if(num[down[hdown]]-num[up[hup]]>=m)
ans=max(ans,tail-head+1);
}
printf("%d\n",ans);
}
return 0;
}