原题链接 https://www.luogu.org/problemnew/show/P1440
今天下午本来想刷一道ST表的题温习一下RMQ问题,点开算法标签找到了这个题。
草草看了一下题面,果然是RMQ问题,只不过询问区间的方式不一样了qwq。
自信的我打上了ST表模板,可是只有80分,有两个点MLE了:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int read()
{
char ch=getchar();
int a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<3)+(a<<1)+(ch-'0');
ch=getchar();
}
return a*x;
}
int n,m;
int a[2000001],f[2000001][20];
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
f[i][0]=a[i];
}
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
cout<<0<<endl;
if(n==1) return 0;
for(int i=2;i<=n;i++)
{
int l=i-m;
int r=i-1;
if(l<1) l=1;
int len=r-l+1;
int t=(int)(double)((double)log(len)/log(2.0));
printf("%d\n",min(f[l][t],f[r-(1<<t)+1][t]));
}
return 0;
}
急忙看了一眼n的范围:
显然f[2000001][20]爆掉了空间!
然后想了好多种方法来节省空间:将数组定义为short类型的,用滚动数组……之类的神奇东西,可是好像还是不大行。
旁边的gh学长瞅了一眼,“这不是单调队列模板题嘛?”他不屑地说。
可是我们还没学单调队列呢,于是gh学长就给我讲起了单调队列qwq。
具体讲的啥早忘辽,只知道这个队列里的元素要么单调递增,要么单调递减。(这不是定义嘛)然后gh学长敲了一下单调队列的代码,没做出来!然后他就找了一篇单调队列的题解让我看(大雾
刚看完一些资料,学得也不是很深,给不懂的同学浅谈一下单调队列吧(说的不对的地方麻烦各位大佬指出,我会及时改正的):
定义:
单调队列,即单调递减或单调递增的队列。
用途——解决“滑动窗口”的问题:
如下图,给出一个长度为n的序列A,求A中所有长度为m的连续子序列的最大值。下图中假设n=7,m=3。
这题只需枚举每个连续子序列,使用单调队列得出最大值即可。
浅谈原理及实现:
对于此题,它要求的是每个滑动窗口的最大值,所以我们要创一个单调递减的单调队列!
1.当单调队列有新元素插入时,我们将队列里所有比该元素小的元素弹出队列(维护递减的性质);
2.然后判断队首是否已经超出滑动窗口的范围,若超出,则将该元素从队首弹出;
3.直接返回队首元素;
单调队列的几个操作:
1.弹出队首元素:q.pop_front();
2.弹出队尾元素:q.pop_back();
3.将元素插到队尾:q.push_back();
4.访问队首元素下标:q.front();
5.访问队尾元素下标:q.back();
6.判断队列是否为空:q.empty();
对于这个题应该就用到这么多了吧。
这个题的滑动窗口是[i-m,i-1],是不包含当前点i的,所以一个很自然的想法就是枚举每个点去维护它的前一个点;
AC代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue> //提供单调队列的函数库
using namespace std;
struct member //定义结构题比较方便
{
int id,value; //id是下标,value是权值
}a[2000008];
int read() //快读
{
char ch=getchar();
int a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<3)+(a<<1)+(ch-'0');
ch=getchar();
}
return a*x;
}
int n,m;
deque<member> q; //定义一个结构体类型的单调队列q,还是递增队列,来保证队首元素是最小值
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
a[i].value=read();
a[i].id=i;
}
cout<<0<<endl; //第一个永远是0
if(n==1) return 0; //n==1直接结束程序
for(int i=2;i<=n;i++)
{
while(!q.empty()&&q.back().value>=a[i-1].value) q.pop_back(); //从队尾开始找,一直将权值比上一个元素大的全部从队尾弹出,这里维护的是上一个点
q.push_back(a[i-1]);//将上一个元素入队
while(q.front().id<i-m) q.pop_front(); //将不在滑动窗口的范围内的元素全部从队首弹出
printf("%d\n",q.front().value); //此时队首元素肯定是符合条件的最小值了,直接用printf输出,cout输出会TLE三个点qwq
}
return 0;
}
当然,我也是一条有想法的咸鱼!!!我也有个不自然的想法,那就是:我们不必让每个点去维护上一个点,让它们管好自己就行了:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue> //提供单调队列的函数库
using namespace std;
struct member //定义结构题比较方便
{
int id,value; //id是下标,value是权值
}a[2000008];
int read() //快读
{
char ch=getchar();
int a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<3)+(a<<1)+(ch-'0');
ch=getchar();
}
return a*x;
}
int n,m;
deque<member> q; //定义一个结构体类型的单调队列q,还是递增队列,来保证队首元素是最小值
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
a[i].value=read();
a[i].id=i;
}
cout<<0<<endl; //第一个永远是0
if(n==1) return 0; //n==1直接结束程序
q.push_back(a[1]); //手动将第一个元素入队
for(int i=2;i<=n;i++)
{
while(!q.empty()&&q.back().value>=a[i].value) q.pop_back(); //从队尾开始找,一直将权值比该元素大的全部从队尾弹出,这里维护的是当前点
while(q.front().id<i-m) q.pop_front(); //将不在滑动窗口的范围内的元素全部从队首弹出
printf("%d\n",q.front().value); //此时队首元素肯定是符合条件的最小值了,直接用printf输出,cout输出会TLE三个点qwq
q.push_back(a[i]);//调换一下入队的顺序,把它调在输出之后就不会造成影响了
}
return 0;
}
但是我都将a数组的范围改到了3e6还是蜜汁RE,好奇怪哦~
另外再附上单调队列的板子题:P1886 滑动窗口
这里我是用的两个单调队列,一个维护最大值(递减),一个维护最小值(递增),然后就解决问题啦,挺快的:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue> //提供单调队列的函数库
using namespace std;
struct member //定义结构题比较方便
{
int id,value; //id是下标,value是权值
}a[1000008];
int read() //快读
{
char ch=getchar();
int a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<3)+(a<<1)+(ch-'0');
ch=getchar();
}
return a*x;
}
int n,m;
int maxn[1000001],minn[1000001];
deque<member> q;
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
a[i].value=read();
a[i].id=i;
}
for(int i=1;i<=n;i++)
{
while(!q.empty()&&q.back().value>=a[i].value) q.pop_back();
q.push_back(a[i]);
while(q.front().id<i-m+1) q.pop_front();
if(i>=m) printf("%d ",q.front().value);
}
cout<<endl;
while(!q.empty()) q.pop_back();
for(int i=1;i<=n;i++)
{
while(!q.empty()&&q.back().value<=a[i].value) q.pop_back();
q.push_back(a[i]);
while(q.front().id<i-m+1) q.pop_front();
if(i>=m) printf("%d ",q.front().value);
}
return 0;
}
来源:oschina
链接:https://my.oschina.net/u/4412725/blog/3514421