昨天才把单调队列做完第一个例题,怎么说咧,就是运用双向队列的性质对数据进行操作。
还是先说说什么是单调队列吧,广义的说法都是队列中元素呈现单调性,也就是说队列里面的家伙们根据某原则存在,听起来玄乎玄乎的,我当时就纳闷了,我第一个想到的就是“难道还要排序?”,其实是这样的,每个元素在入队之前会进行比较,从队尾开始,如果插入当前的中国元素会破环单调性,那么把用来跟他比较的队尾元素出队处理,然后再比较,注意,这是用来比较的队尾元素是现在队尾元素的前面那个,因为先前那个被剔除了,直到当前元素压入队列也满足单调性,这是就结束筛选,把当前元素压入队列
最好的是举个例子来,比如a[10]={1,3,5,7,9,0,2,4,6,8}那么我用16行来模拟每次的队列里面装的元素,****下面为单调递增
1、NULL 一开始队列内为空
2、1 把空当作无穷小就好了,1比无穷小大,所以1入队
3、1 3 因为3比1大,所以3入队
4、1 3 5 因为5比3大,所以5入队
5、1 3 5 7 因为7比5打,所以7入队
6、1 3 5 7 9 因为9比7大,所以9入队
7、1 3 5 7 这时因为0比9小,所以9出队
8、1 3 5 继续用队尾元素跟0比,因为7还是比0大,所以7出队
9、1 3 同上,因为0比5小,所以5出队
10、1 同上,因为0比3小,所以3出队
11、NULL 同上,因为0比1小,所以1出队,此时队列又回到空的状态栏
12、0 因为队列为空,所以0入队
13、0 2 因为2比0大,所以入队
14、0 2 4 因为4比2大,所以4入队
15、0 2 4 6 因为6比4大,所以6入队
16、0 2 4 6 8 因为8比6大,所以8入队
如果你好好看了一下,那发现了所谓的单调性只是队列中呈现的有木有?哦耶,恭喜恭喜,你已经知道单调队列的精华了,下面给你弄个例题,你就知道怎么去用它了
嘿嘿,no好意思,我也是初学,所以只做了一个题,但是别狗眼看人低,认识不同的哦~亲,没有领略真谛可不能出来吓人呢
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4883 Accepted Submission(s): 1780
用两种写法弄过,一种调用STL双向队列,一种是直接数组模拟,感觉模拟麻烦点但是耗时很少,双向队列每次都是调用的函数,所以耗时多。
STL双向队列:(734MS)
#include
#include
using namespace std;
int a[222222],sum[222222]; //sum[n]记录从a[1]加到a[n]的和
deque<int> q;
int main (void)
{
int t,n,m,i,head,end,s;
cin>>t;
while(t--&&cin>>n>>m)
{
sum[0]=0;
for(i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
for(i=n+1;i<=n+m;i++)
sum[i]=sum[i-1]+a[i-n];
s=-1e10;head=end=0; //最大值初始化为-1e10
q.clear(); //把队列弄干净,初始化双向队列,清空
for(i=1;i<n+m;i++)
{
while(!q.empty()&&sum[i-1]<sum[q.back()]) //如果队列不是空的,而且当前数值压入队列会破坏队列元素单调
q.pop_back(); //那么把队尾元素出队
while(!q.empty()&&i-q.front()>m) //如果队列不是空的,而且当前记录的子串长度超过规定范围
q.pop_front(); //队首元素出队
q.push_back(i-1); //把当前数值压入队尾
if(sum[i]-sum[q.front()]>s) //当前元素满足条件的最大值大于记录的最大值时进行记录操作
{
s=sum[i]-sum[q.front()];
head=q.front()+1;end=i;
}
}
if(head>n)head-=n;
if(end>n)end-=n;
cout<<s<<" "<<head<<" "<<end<<endl;
}
return 0;
}
用数组模拟双向队列:(187MS)
#include
#include
#include
using namespace
int a[222222],sum[222222]={0},que[222222]; //que代替队列起记录作用,sum还是一样记录从1到n的和
int main (void)
{
int t,n,m,s,i,j,k,l,max,aa,ss,qian,hou;
scanf("%d",&t);
while(t--&&scanf("%d%d",&n,&m))
{
for(i=1;i<=n;i++)
scanf("%d",&a[i]),a[n+i]=a[i];
for(i=1,sum[0]=0;i<=n+m;i++)
sum[i]=sum[i-1]+a[i];
max=-1000000,aa=ss=qian=hou=0;
for(i=1;i<n+m;i++)
{
while(qian<hou&&sum[i-1]<sum[que[hou-1]])hou--; //hou即是队尾
que[hou++]=i-1; //压入队列数组
while(qian<hou&&i-que[qian]>m)qian++; /qian即是队首
if(sum[i]-sum[que[qian]]>max)
{
max=sum[i]-sum[que[qian]];
aa=que[qian]+1;ss=i;
}
}
if(aa>n)aa-=n;
if(ss>n)ss-=n;
printf("%d %d %d\n",max,aa,ss);
}
return 0;
}
有木有发现队列处理的是他们的和?你要明白sum[2]=a[0]+a[0]+a[2], sum[1]=a[0]+a[1],于是有sum[2]-sum[1]=a[2] 因为题目是要求最长连续的子序列,所以用这个可以相减直接得到咯,同时我再保证队列里面的跨度不超过m就OK了撒,看清楚了,队列里面不是装的数组a的值,也不是sum的,是下标,所以计算当前长度就直接用i减去队首就行了,里面有俩while循环,上面那个是用来保持队列中单调性的,也就是直到加入i之后会破坏单调性的最后一个队尾元素滚出去之后就结束,这是就毫不小气地把i请入茅房,第二个while循环是用来保持快读不超过m的,题目是限制了最长长度的,所以这个得注意,然后就是if判断了,只要sum[i]-sum[队首元素]得到的更大,那么就取代
还要明确一点,就是用STL里面的双向队列和自己用数组模拟双向队列,前者的入队出队等所有操作都是调用的函数,所以时间问题你懂的,嘿嘿,后者的话就注意模拟数组的大小就行了,你想啊,队首出队一个那队首下标就要后移一个,要是移动多了...你也懂的,嘿嘿,经过我疯狂的文字教育,亲~还木有懂的请举手