【问题描述】
地理学家们经常要对一段河流进行测量分析。他们从上游开始向下游方向等距离地选择n(n<=30000)个点测量水位深度。得到一组数据a1,a2,…,an,回到实验室后数据分析员根据需要对数据进行分析,发掘隐藏在数据背后的规律。
最近,乌龙博士发现某种水文现象与河床地势有关,于是他指示分析员要找出一段河流中最大高低起伏差不超过K(1<=K<=100)的最长的一段。这看似一个简单的问题,由于任务紧急,分析员来求助于你,并告诉你博士的所有数据都精确到个位。
【输入格式】
第一行包含两个整数N和K,分别表示测量点的个数和博士要求的最大水深度(也就是河床的地势差)。
第二行包含N个整数,表示从上游开始依次得到的水位深度di(1<=i<=n,0<=di<=32767)
【输出格式】
一个整数m,表示最长一段起伏不超过K的河流长度,用测量点的个数表示
【输入样例】
6 2
5 3 2 2 4 5
【输出样例】
4
【数据范围】
30%的数据,满足:1<=n<=5000;
50%的数据,满足:1<=n<=30000,符合条件的最长连续序列长度不超过100;
100%的数据,满足:1<=n<=30000,符合条件的最长连续序列长度不超过n;
【来源】
从第2个测量点到第5个测量点之间的一段,即3 2 2 4,起伏最大为2。
【原题传送矩阵】
山东理工大学ACM题库 1128
Code[VS]题库 1809 传送矩阵
CQYZOJ 1823 传送矩阵
【思路梳理】
看到网上很多人的题解都是交的暴力搜索代码,直接从1~n来枚举起点,看此时能够实现的最长长度。算法是没有任何问题的,但是有追求的ACMer不应该止步于此(滑稽脸)。笔者此处提供更为高效的滑动窗口算法,供读者们茶余饭后笑谈。
简单整理一下可以将题目简化成如下的形式:在线性序列上,求最长的一段,使得该段中最大值与最小值的差不超过一个给定定值k。线段的最大长度是我们所求值。典型的变长滑动窗口问题。
设置两个滑动窗口进行存储,一个最大队,一个最小队。不断地将线性序列的元素加入到两个滑动窗口中,直到最大队队首highest与最小队队首元素lowest的差值超过了给定值k。
此时,我们就确定一下highest与lowest的相对位置。为了保证我们剩下来的滑动窗口尽可能的长,那么我们应该让highest与lowest两者当中较靠左边的一个,让它离开滑动窗口,并更新两者。如此反复维护这个滑动窗口,使得highest与lowest的差值不超过k。
在让新的元素进入滑动窗口以前,更新一次我们的答案ans即可。
笔者语拙,更多说明详见注释。
【Cpp代码】
#include<queue>
#include<cstdio>
#include<iostream>
#define maxn 30005
using namespace std;
int n,k,last=1;
struct data
{int height,id;}a[maxn];
//滑动窗口中元素两个属性:距离上游的位置和深度。
//一个height用于维护滑动窗口,另一个id用于计算ans
struct cmp1{bool operator()(data a,data b){return a.height<b.height;}};//定义最大队的仿函数
struct cmp2{bool operator()(data a,data b){return a.height>b.height;}};//最小队仿函数
void solve()
{
int ans=1,last=1;//last:当前滑动窗口最左侧元素的id,ans最小值应该是1,只有1个元素是合法解
priority_queue<data,vector<data>,cmp1>q1;
priority_queue<data,vector<data>,cmp2>q2;
q1.push(a[1]);q2.push(a[1]);//值得强调的是,两个滑动窗口维护的是同一段序列!
data highest=q1.top(),lowest=q2.top();
for(int i=2;i<=n;i++)
{
q1.push(a[i]);q2.push(a[i]);
highest=q1.top(),lowest=q2.top();//新元素进入滑动窗口后应该更新这两个元素的值
while(highest.height-lowest.height>k)//当该段序列的起伏差大于了k时
{
if(highest.id<=lowest.id)//最大队队首元素靠左,根据贪心的思想应该使得滑动窗口尽可能长
{
last=max(last,highest.id+1);//注意+1,highest元素是要出滑动窗口的
q1.pop();
}
if(highest.id>lowest.id)//最小队队首元素靠左
{
last=max(last,lowest.id+1);
q2.pop();
}
highest=q1.top();//不忘再次更新
lowest=q2.top();//这一次是可以省略的
}
ans=max(ans,i-last+1);
}
printf("%d",ans);
}
int main()
{
// freopen("in.txt","r",stdin);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i].height),a[i].id=i;
solve();
}