题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5289
2 4 2 3 1 2 4 10 5 0 3 4 5 2 1 6 7 8 9
5 28HintFirst Sample, the satisfied groups include:[1,1]、[2,2]、[3,3]、[4,4] 、[2,3]
题意:
找出连续子区间(满足最大值与最小值的差小于k)的个数。
分析:
方法1:如果直接暴力,复杂度为O(n^2)。而暴力所浪费的时间在于重复算了已经算的区间。比如第一次算得1~10区间都满足,那么2~10肯定也满足,所以以此类推可知,如果一个区间的起点包括在已算的区间内,那么对于这个点的右端点就可以从已算区间的右端点开始往右遍历。然后利用multiset这个STL容器每次添加右边的元素,同时删除左边的元素,那么就可以直接知道区间的最大值与最小值,而插入删除的时间复杂度为O(logn)。如果这样从左到右遍历的话,总共也只需遍历n个点,所以最终时间复杂度为O(nlogn)。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5+10; int main() { int T, n, k, a[N]; scanf("%d", &T); while(T--) { scanf("%d%d", &n,&k); for(int i=0; i<n; i++) scanf("%d", a+i); multiset<ll> ms; multiset<ll>::iterator its, ite; ll t = 0, res = 0; for(ll i=0; i<n; i++) { its = ms.begin(); ite = ms.end(); if(!ms.empty()) ite--; else { ms.insert(a[i]); ite = ms.begin(); its = ms.begin(); t++; } while(t<n && (*ite)-(*its)<k) { ms.insert(a[t++]); its = ms.begin(); ite = --ms.end(); } res += t-i; ms.erase(ms.find(a[i])); } printf("%lld\n", res); } return 0; }
利用单调队列,思想跟上面的差不多,是尺取法的思想。
要特别注意单调队列的特性,假如插入了一个原本单调递增的序列到递增的单调队列中,那么序列不变。如果将其插入到递减的单调队列中,就会只保留值最大的元素。其实也就是队列中保留最大值或最小值,使序列单调,以最新的数为主。
(单调队列好像也被称为双端队列,队列的头和尾都可以插入以及弹出操作。一般用来求区间最值,队列中存的元素都是下标。例如求区间最大值:保证队头元素对应值最大,元素对应值从头到尾单调递减。插入元素时,将要插入元素对应的值与队尾元素对应值比,如果插入元素对应值小就舍去,大的话就弹出队尾元素,再跟现在的队尾元素对应值比,直到队列中没有比要插入元素对应值小的再将要插入元素插入到队尾。相等的话也是要更新的。最大值就是队头。如果队头元素"过期了"就把它从头部弹出。)
#include<stdio.h> #include<queue> using namespace std; typedef long long ll; const int N = 1e5+10; int main() { int T, n, k, a[N]; scanf("%d", &T); while(T--) { scanf("%d%d", &n,&k); for(int i=0; i<n; i++) scanf("%d", a+i); deque<int> q1,q2; //q1递增,q2递减 ll res = 0, head = 0; for(int i=0; i<n; i++) { while(!q1.empty() && a[q1.back()]>=a[i]) q1.pop_back(); q1.push_back(i); //这两行是单调队列的元素插入 while(!q2.empty() && a[q2.back()]<=a[i]) q2.pop_back(); q2.push_back(i); //插入元素的下标 while(1) { int index1 = q1.front(); int index2 = q2.front(); int minn = a[index1]; int maxn = a[index2]; if(maxn-minn < k) break; if(index1 < index2) //当插入的原本序列为递增序列时 { head = index1+1; q1.pop_front(); } else //如果原本是一个递减序列 { head = index2+1; q2.pop_front(); } } res += i-head+1; } printf("%lld\n", res); } return 0; }