POJ2823 Sliding Window (单调队列)
Sliding Window
Description An array of size n ≤ 106 is given to you. There is a sliding window of size k which 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.
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 Source POJ Monthly--2006.04.28, Ikki
|
大意:
看题目中那个表格很容易懂,就是有个滑动区间,每次我们要找到这个区间中的最大值、最小值。
题解:
单调队列。
单调队列是DP优化的一种,能实现O(VN)的多重背包。这题虽然不是DP,不过能怒用一发单调队列。
单调队列是用一个单调的队列来存储必要的元素,并不存储无用的元素。在这题的求最小值的过程中:
元素按顺序读入队列q,当队列为空时直接读入;当队列非空时,若队尾的元素不小于当前要入队的元素,则踢出队尾,直到队尾元素小于要入队的元素或者队为空为止。
再开一个数组p,存储队列中的元素在原数据中的下标。
像这样:
(读元素a[i])
1 while(l<r && q[r-1]>=a[i]) r--; 2 p[r]=i; 3 q[r++]=a[i];
然后检查队首,如果p存储的队首的元素下标表示该元素已经滑出区间,则将其踢出,像这样:
(检查队首、踢出过期元素)
while(p[l]<i-k+1) l++;///若队首的下标表示它已过期(滑出了区间),弹出队首
经过这2个操作后,q[l]就是所求的当前区间的最小值。然后i++,再进行这2个操作,就能得到下一个最小值。换一个符号就能求最大值了。
这两个操作有什么深意呢?
读取时那样的操作,保证了队列中存储了递增的最小若干个数,在队首能立即得到当前区间最小的数(若那个数已经滑出了区间,则它会被踢出),
当那个数滑出区间时能立即找到下一个最小的数。
就这两个简单的操作,用极低的复杂度,就能完成找区间滑动到每个地方的最小值/最大值!我就问你碉不碉!
代码:
1 #include<cstdio> 2 #include<cmath> 3 #include<iostream> 4 #include<cstring> 5 #include<algorithm> 6 #include<cmath> 7 #include<map> 8 #include<set> 9 using namespace std; 10 #define ll __int64 11 #define usint unsigned int 12 #define mz(array) memset(array, 0, sizeof(array)) 13 #define minf(array) memset(array, inf, sizeof(array)) 14 #define REP(i,n) for(int i=0;i<(n);i++) 15 #define FOR(i,x,n) for(int i=(x);i<=(n);i++) 16 #define WR(x) printf("%d ",(x)) 17 #define FR freopen("1.in","r",stdin) 18 #define FW freopen("1.out","w",stdout) 19 20 const int maxn=1111111; 21 22 int n,k; 23 int a[maxn];///存储数据 24 int q[maxn];///队列 25 int p[maxn];///存储a[i]的下标i 26 27 void gankmin() 28 { 29 int i,l=0,r=0;///l,r为队列头尾 30 for(i=0;i<k-1;i++)///先将前k-1个入队 31 { 32 while(l<r && q[r-1]>=a[i]) r--;///队列有元素时,要保证队尾小于要入队的元素,否则弹出队尾 33 ///意义在于存储递增的最小若干个数,能立即得到当前区间最小的数, 34 ///当那个数滑出区间时能立即找到下一个最小的数 35 p[r]=i; ///记录a[i]的下标 36 q[r++]=a[i];///a[i]加入队列 37 } 38 for(;i<n;i++) 39 { 40 while(l<r && q[r-1]>=a[i]) r--; 41 p[r]=i; 42 q[r++]=a[i]; 43 44 while(p[l]<i-k+1) l++;///若队首的下标表示它已过期(滑出了区间),弹出队首 45 WR(q[l]); 46 } 47 } 48 49 void gankmax() 50 { 51 int i,l=0,r=0; 52 for(i=0;i<k-1;i++) 53 { 54 while(l<r && q[r-1]<=a[i]) r--; 55 p[r]=i; 56 q[r++]=a[i]; 57 } 58 for(;i<n;i++) 59 { 60 while(l<r && q[r-1]<=a[i]) r--; 61 p[r]=i; 62 q[r++]=a[i]; 63 64 while(p[l]<i-k+1) l++; 65 WR(q[l]); 66 } 67 } 68 69 int main() 70 { 71 while(scanf("%d%d",&n,&k)!=EOF) 72 { 73 REP(i,n) 74 scanf("%d",&a[i]); 75 gankmin(); 76 puts(""); 77 gankmax(); 78 puts(""); 79 } 80 return 0; 81 }