【POJ 2823 Sliding Window】 单调队列

题目大意:给n个数,一个长度为k(k<n)的闭区间从0滑动到n,求滑动中区间的最大值序列和最小值序列。

最大值和最小值是类似的,在此以最大值为例分析。

数据结构要求:能保存最多k个元素,快速取得最大值,更新时删去“过期”元素和“不再有希望”的元素,安放新元素。

单调队列的基本概念百度百科讲得比较清楚了:http://baike.baidu.com/view/3771451.htm

 

我的大致思路是:

1. 每个元素存储为结构体,包含它的秩和值。维护最大长度为k的单调队列,保证所有元素的秩都在区间内,且从首到尾的元素,秩递增,值递减。

2. 读入前k个元素(不存在过期问题),安放每个元素c:从队尾开始往回找到第一个大于它的元素g,将c放到g后面,c成为新的队尾

3. 队首赋给最大值序列的第一个值。

4. 读入k~n-1的元素,每读入一个元素c:

  (1)处理队首的过期元素(每次最多只可能是队首一个元素过期,因为队列长度不超过k,且秩是单调增的)

  (2)安放c(方法同前k个元素)

  (3)将新队首赋给最大值序列的下一个值

5. 输出最大值序列

想清楚了的话,代码还是比较好写的;队列没有封装,简单地用数组+首尾指针实现:

 1 #include <cstdio>
 2 #include <cstring>
 3 using namespace std;
 4 
 5 struct Node
 6 {
 7     int index;
 8     int value;
 9 };
10 
11 Node max_q[2000002],min_q[2000002];
12 int max_res[1000002],min_res[1000002];
13 int front_max,front_min;//队首指针
14 int back_max,back_min;//队尾指针
15 int n,k,c;
16 
17 int main()
18 {
19     //freopen("c.txt","r",stdin);
20     scanf("%d%d",&n,&k);
21     back_max=back_min=front_max=front_min=0;
22     scanf("%d",&c);
23     max_q[back_max].value=c;
24     min_q[back_min].value=c;
25     max_q[back_max].index=0;
26     min_q[back_min].index=0;//用第一个元素初始化
27     for(int j=1;j<k;j++)//前k个元素
28     {
29         scanf("%d",&c);
30         while(back_max>=0 && max_q[back_max].value<=c) back_max--;
31         max_q[++back_max].value=c;
32         max_q[back_max].index=j;
33 
34         while(back_min>=0 && min_q[back_min].value>=c) back_min--;
35         min_q[++back_min].value=c;
36         min_q[back_min].index=j;
37 
38     }
39     max_res[0]=max_q[0].value;//区间起始位置的最值
40     min_res[0]=min_q[0].value;
41 
42     for(int j=k;j<n;j++)//下标为k到n-1的元素
43     {
44         scanf("%d",&c);
45         if(max_q[front_max].index==j-k) front_max++;
46         if(min_q[front_min].index==j-k) front_min++;
47 
48         while(back_max>=front_max && max_q[back_max].value<=c) back_max--;
49         max_q[++back_max].value=c;
50         max_q[back_max].index=j;
51 
52         while(back_min>=front_min && min_q[back_min].value>=c) back_min--;
53         min_q[++back_min].value=c;
54         min_q[back_min].index=j;
55 
56         max_res[j-k+1]=max_q[front_max].value;//每读入一个元素,更新一次区间,得到一个最值
57         min_res[j-k+1]=min_q[front_min].value;
58     }
59 
60     for(int j=0;j<n-k+1;j++)
61         printf("%d ",min_res[j]);
62     printf("\n");
63     for(int j=0;j<n-k+1;j++)
64         printf("%d ",max_res[j]);
65     printf("\n");
66     return 0;
67 }

OJ的结果是这样的(G++会超时,尚不明原因):

最开始没有考虑过期的问题,考虑之后担心队列不够长,需不需要写成循环的;但稍加分析会发现,front指针后移只发生在删除队首过期元素时,最多只发生n-k次,那么数组开到2n就可以了。

由于是不循环的队列,只需front和back两个指针就可完成所有需要的操作。(之前因为和一个计数变量混用,并是在边界判断时WA了很多次)

把前k个元素和之后的元素分开处理是为了考虑方便,AC了之后试图把它们合并起来然后并是又WA了。。。看来有时候不必过于追求代码的简炼,初学还是清晰更重要。

posted @ 2015-08-29 22:03  helena_wang  阅读(205)  评论(0编辑  收藏  举报