不要让昨天 占据你的今天 夏午晴天

夏午晴天

[POJ-2823] -Sliding Window

Sliding Window
Time Limit: 12000MS   Memory Limit: 65536K
Total Submissions: 56028   Accepted: 16112
Case Time Limit: 5000MS

An array of size n ≤ 10 6 is given to you. There is a sliding window of size kwhich is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves rightwards by one position. Following is an example: 
The array is [1 3 -1 -3 5 3 6 7], and k is 3.
Window positionMinimum valueMaximum value
[1  3  -1] -3  5  3  6  7  -1 3
 1 [3  -1  -3] 5  3  6  7  -3 3
 1  3 [-1  -3  5] 3  6  7  -3 5
 1  3  -1 [-3  5  3] 6  7  -3 5
 1  3  -1  -3 [5  3  6] 7  3 6
 1  3  -1  -3  5 [3  6  7] 3 7

Your task is to determine the maximum and minimum values in the sliding window at each position. 

Input

The input consists of two lines. The first line contains two integers n and k which are the lengths of the array and the sliding window. There are n integers in the second line. 

Output

There are two lines in the output. The first line gives the minimum values in the window at each position, from left to right, respectively. The second line gives the maximum values. 

Sample Input

8 3
1 3 -1 -3 5 3 6 7

Sample Output

-1 -3 -3 -3 3 3
3 3 5 5 6 7

题意: 给定一行数,共N个。有一个长度为K的窗口从左向右滑动,窗口中始终有K个数字,窗口每次滑动一个数字。求各个时刻窗口中的最大值和最小值。

 

一、概念介绍
1、 双端队列
双端队列是一种线性表,是一种特殊的队列,遵守先进先出的原则。双端队列支持以下4种操作:

(1)   从队首删除
(2)   从队尾删除
(3)   从队尾插入
(4)   查询线性表中任意一元素的值
2、 单调队列
单调队列是一种特殊的双端队列,其内部元素具有单调性。最大队列与最小队列是两种比较常用的单调队列,其内部元素分别是严格单调递减(不是非递增)和严格单调递增(不是非递减)的。

单调队列的常用操作如下:

(1) 插入:若新元素从队尾插入后会破坏单调性,则删除队尾元素,直到插入后不再破坏单调性为止,再将其插入单调队列。
(2) 获取最优(最大、最小)值:访问队首元素
以下是一个单调递增队列的例子:

队列大小不能超过3,入队元素依次为3,2,8,4,5,7,6,4

3入队:(3)

3从队尾出队,2入队:(2)

8入队:(2,8)

8从队尾出队,4入队:(2,4)

5入队:(2,4,5)

2从队头出队,7入队:(4,5,7)

7从队尾出队,6入队:(4,5,6)

6从队尾出队,5从队尾出队,4从队尾出队,4入队:(4)

以上左端为队头,右端为队尾。

二、单调队列的应用
1、最大值的维护:

       比如我们要维护一个区间为k的最大值的单调队列,由于新插入 的节点他的“生命力”肯定比原先已经在队列中的元素“活”的时间长,将插入元素不断与队尾元素比, 如果他大于队尾元素,那么tail--将队尾元素删掉,(因为目前插入的这个元素值(设为pos)更大,而且“活”的时间 长,有pos在,队尾元素的有“生”之年永远都没法为最大值,故而直接无视比pos小的队尾了)。直到对空位置或者 找到了一个比pos大的队尾。 

这个问题相当于一个数据流(数列a)在不断地到来,而数据是不断过期的,相当于我们只能保存有限的数据(sliding window中的数据,此题中就是窗口的宽度w),对于到来的查询(此题中查询是每时刻都有的),我们要返回当前滑动窗口中的最大值最小值。注意,元素是不断过期的。

解决这个问题可以使用一种叫做单调队列的数据结构,它维护这样一种队列:

a)从队头到队尾,元素在我们所关注的指标下是递减的(严格递减,而不是非递增),比如查询如果每次问的是窗口内的最小值,那么队列中元素从左至右就应该递增,如果每次问的是窗口内的最大值,则应该递减,依此类推。这是为了保证每次查询只需要取队头元素。
b)从队头到队尾,元素对应的时刻(此题中是该元素在数列a中的下标)是递增的,但不要求连续,这是为了保证最左面的元素总是最先过期,且每当有新元素来临的时候一定是插入队尾。
满足以上两点的队列就是单调队列,首先,只有第一个元素的序列一定是单调队列。

那么怎么维护这个单调队列呢?无非是处理插入和查询两个操作。

  对于插入,由于性质b,因此来的新元素插入到队列的最后就能维持b)继续成立。但是为了维护a)的成立,即元素在我们关注的指标下递减,从队尾插入新元素的时候可能要删除队尾的一些元素,具体说来就是,找到第一个大于(在所关注指标下)新元素的元素,删除其后所有元素,并将新元素插于其后。因为所有被删除的元素都比新元素要小,而且比新元素要旧,因此在以后的任何查询中都不可能成为答案,所以可以放心删除。

        对于查询,由于性质b,因此所有该时刻过期的元素一定都集中在队头,因此利用查询的时机删除队头所有过期的元素,在不含过期元素后,队头得元素就是查询的答案(性质a),将其返回即可。由于每个元素都进队出队一次,因此摊销复杂度为O(n)。

模板:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 using namespace std;
 6 const int maxn = 1e6 + 5;
 7 struct node
 8 {
 9     int pos, val;
10 }q[maxn];
11 int k, num[maxn], n, Min[maxn], Max[maxn];
12 void getmin()
13 {
14     int head = 1, tail = 0;
15     for(int i = 1; i < k; i++)
16     {
17         while(head <= tail && num[i] < q[tail].val) tail--;
18         tail++;
19         q[tail].val = num[i];
20         q[tail].pos = i;
21     }
22     for(int i = k; i <= n; i++)
23     {
24         while(head <= tail && num[i] < q[tail].val) tail--;
25         tail++;
26         q[tail].val = num[i];
27         q[tail].pos = i;
28         while(i-q[head].pos >= k) head++;
29         Min[i-k] = q[head].val;
30     }
31 }
32 void getmax()
33 {
34     int head = 1, tail = 0;
35     for(int i = 1; i < k; i++)
36     {
37         while(head <= tail && num[i] > q[tail].val) tail--;
38         tail++;
39         q[tail].val = num[i];
40         q[tail].pos = i;
41     }
42     for(int i = k; i <= n; i++)
43     {
44         while(head <= tail && num[i] > q[tail].val) tail--;
45         tail++;
46         q[tail].val = num[i];
47         q[tail].pos = i;
48         while(i - q[head].pos >= k) head++;
49         Max[i-k] = q[head].val;
50     }
51  
52 }
53 int main()
54 {
55     while(~scanf("%d%d", &n, &k))
56     {
57         for(int i = 1; i <= n; i++) scanf("%d", &num[i]);
58         getmin();
59         for(int i=0; i<=n-k; i++) printf("%d%c",Min[i],i==n-k?'\n':' ');
60         getmax();
61         for(int i=0; i<=n-k; i++) printf("%d%c",Max[i],i==n-k?'\n':' ');
62     }
63     return 0;
64 }

用优先队列写的:操作的是下标

 1 #include<algorithm>
 2 #include<queue>
 3 #include<vector>
 4 using namespace std;
 5 int a[1000011];//数组数据
 6  
 7 int OutMin[1000011];//最小值
 8  
 9 int OutMax[1000011];//最大值
10  
11 int cnt1=0;
12 int cnt2=0;
13 int n,k;
14  
15 struct cmp1
16 {
17     bool operator()(const int a1,const int a2)
18     {
19         return a[a1]>a[a2];  //这里太精髓,重定义的是数组元素值,但是队列存的是下标
20     }
21 };
22 struct cmp2
23 {
24     bool operator()(const int a1,const int a2)
25     {
26         return a[a1]<a[a2];
27     }
28 };
29 priority_queue <int ,vector<int>,cmp1> Q1;  //重定义符号
30 priority_queue <int ,vector<int>,cmp2> Q2;
31 int main()
32 {
33     int i;
34     scanf("%d%d",&n,&k);
35     if(k>n)
36         k=n;
37     for(i=1;i<=n;++i)
38     {
39         scanf("%d",&a[i]);
40     }
41     for(i=1;i<=k;++i)
42     {
43         Q1.push(i);
44         Q2.push(i);
45     }
46     OutMin[cnt1++]=a[Q1.top()];
47     OutMax[cnt2++]=a[Q2.top()];
48     for(i=k+1;i<=n;++i)
49     {
50         Q1.push(i);
51         Q2.push(i);
52         while(i-Q1.top()>=k)
53             Q1.pop();
54         OutMin[cnt1++]=a[Q1.top()];
55         while(i-Q2.top()>=k)
56             Q2.pop();
57         OutMax[cnt2++]=a[Q2.top()];
58     }
59     
60     for(i=0;i<=(n-k);++i)
61     {
62         printf("%d%c", OutMin[i], (i < n - k) ? ' ' : '\n'); 
63         
64     }
65     for(i=0;i<=(n-k);++i)
66     {
67         printf("%d%c", OutMax[i], (i < n - k) ? ' ' : '\n'); 
68     }
69     return 0;
70 }

 

posted on 2019-05-05 21:24  夏晴天  阅读(...)  评论(...编辑  收藏

导航

统计

Live2D