AOJ-542-Window/POJ-2823-Window
Description
The array is [1 3 -1 -3 5 3 6 7], and k is 3.
Window position | Minimum value | Maximum 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
Output
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
--------------------------------------------------------------并不华丽的分界线----------------------------------------------------------------------
这道题我在AOJ和POJ上都看到了,所以就一起写了。虽然两题题面一样,但是要求不同,比如AOJ上就是8000ms而POJ上要求是12000ms。然而非常神奇的是POJ上AC的代码在AOJ上一次CE,一次RE。。。本宝宝已经被逗哭。不过最后小小的修改之后都过了。
看到这个题目,其实我一开始的想法就是线段树,而且是最裸的线段树,连单点更新都不需要,所以就撸了一发线段树。代码如下:
#include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #define lson rt << 1 #define rson rt << 1 | 1 using namespace std; const int maxn = 1000010; struct panel { int l, r; int _min, _max; }tree[maxn << 2]; int _min[maxn], _max[maxn]; void build(int rt, int l, int r){ tree[rt].l = l; tree[rt].r = r; tree[rt]._max = 0; tree[rt]._min = 0; if(l == r){ scanf("%d",&tree[rt]._min); tree[rt]._max = tree[rt]._min; return; } int mid = (l + r) >> 1; build(lson, l, mid); build(rson, mid + 1, r); tree[rt]._min = min(tree[lson]._min, tree[rson]._min); tree[rt]._max = max(tree[lson]._max, tree[rson]._max); } int QueryMin(int rt, int l, int r){ int mid = (tree[rt].l + tree[rt].r) >> 1; if(l == tree[rt].l && r == tree[rt].r){ return tree[rt]._min; }else if(r <= mid){ return QueryMin(lson, l, r); }else if(l > mid){ return QueryMin(rson, l, r); }else{ return min(QueryMin(lson, l ,mid), QueryMin(rson, mid + 1, r)); } } int QueryMax(int rt, int l, int r){ int mid = (tree[rt].l + tree[rt].r) >> 1; if(l == tree[rt].l && r == tree[rt].r){ return tree[rt]._max; }else if(r <= mid){ return QueryMax(lson, l, r); }else if(l > mid){ return QueryMax(rson, l, r); }else{ return max(QueryMax(lson, l ,mid), QueryMax(rson, mid + 1, r)); } } int main() { int n,m; while(~scanf("%d%d", &n, &m)){ build(1,1,n); for(int i = 1; (i + m - 1) <= n; i++){ _min[i] = QueryMin(1, i, i+m-1); _max[i] = QueryMax(1, i, i+m-1); } for(int i = 1; (i + m - 1) <= n; i++){ printf("%d%c", _min[i], ((i + m - 1) == n ? '\n' : ' ')); } for(int i = 1; (i + m - 1) <= n; i++){ printf("%d%c", _max[i], ((i + m - 1) == n ? '\n' : ' ')); } } return 0; }<strong> </strong>
但是我这道题并不是为了撸线段树做的,因为当时刚好在看多重背包,又准备去撸楼教的男人八题里面的那道巨蛋疼的多重背包。所以看了多重背包的单调队列优化,然而发现本渣渣并不懂什么是单调队列,于是就去学了一发单调队列。刚好这道题是单调队列里面最最基础的模板题。于是就orz的撸了这个题。代码如下:
#include <cstdio> #include <iostream> using namespace std; const int maxn = 1000010; struct Node { int val; int index; }temp, que[maxn]; int num[maxn]; int ma[maxn]; int mi[maxn]; void GetMax(int len, int k){ int head = 1; int end = 0; for(int i = 0; i < len; i++){ temp.val = num[i]; temp.index = i; while(head <= end && que[end].val <= num[i]){ --end; } ++end; que[end] = temp; while(que[head].index < i-k+1){ ++head; } ma[i] = que[head].val; } } void GetMin(int len, int k){ int head = 1; int end = 0; for(int i = 0; i < len; i++){ temp.val = num[i]; temp.index = i; while(head <= end && que[end].val >= num[i]){ --end; } ++end; que[end] = temp; while(que[head].index < i - k + 1){ ++head; } mi[i] = que[head].val; } } int main() { int n, m; while(~scanf("%d%d",&n,&m)){ for(int i = 0; i < n; i++){ scanf("%d",&num[i]); } GetMax(n, m); GetMin(n, m); for(int i = m-1; i < n; i++){ cout << mi[i] << ((i == n - 1? '\n' : ' ')); } for(int i = m-1; i < n; i++){ cout << ma[i] << ((i == n - 1 ? '\n' : ' ')); } } return 0; }<span style="font-weight: bold;"> </span>
代码贴过了,那就说说单调队列吧。
当我们使用线段树来查询的时候,有些数据是重复被查询的。比如1 3 -1 -3 5 3 6 7,我们会先查询1 3 -1,然后再查询3 -1 -3,再查询-1 -3 5...我们发现每次查询都会有重复查询,这样就会造成复杂度变高。那么,根据经验来想,既然有重复查询,那么肯定是可以把重复查询给省略掉的,这个时候就是单调队列的用处了。
用一个队列来记录查询数据,确保队头为当前查询最大/最小,每当查询新的数据,就与队尾比较,如果查询的是某个区间的最大数据,那么就把这个新的数据一直放到第一个不小于它的数据的后面一个位置,而小于它的数据就都删除,如果队列中所有数据都比这个新的数据小,那么就把这个数据置于队头。而每次查询新数据完成之后,我们就把队头的元素的下标与当前查询的数据的下标比较,如果两者之差大于等于区间长度k,就让队头出队。不难发现,我们这样运算,就没有重复查询的情况了。大大优化了时间。
不过现在单调队列的使用并不多,不过因为有一些算法是建立在单调队列的基础上的,所以最好还是掌握一下吧。