牛客 最大最小(单调栈,区间最大大于最小两倍)
程序员文章站
2022-07-08 17:02:10
...
思路:
首先必须明确单调栈的最主要的作用,就是找到每个数左边右边第一个小于大于他的数,如果配上倍增或者二分可以找到小于大于他的第k大数。所有用单调栈的题目都得思考怎么利用这个性质来套题目。
- 对于本题,我们可以找到每个数右边第一个大于等于其两倍的数,小于等于其一半的数。
- 可以找到以每个数为左端点的可行区间的数目,也就是。
- 但此时有一些数的可行区间没算出来,但他可以与后面的数连接在一起构成可行区间。
- 可以想到如果一个区间可行,则把这个区间扩大仍然可行。所以我们还可以维护b[i]代表以第个数为左端点的可行区间数目,则可以有。也就是用有值的点来更新没有更新过的点,这个套路也很常见了。
- 因为每次只考虑以每个数为左端点的可行区间数目,所以可以保证不会算重。
typedef long long ll;
using namespace std;
const int maxn = 1e5;
const int INF = 0x3f3f3f3f;
struct STK {
int id,v;
}stk[maxn];
int a[maxn],b[maxn];
int R2[maxn]; //右边第一个大于等于其两倍的数
int R3[maxn]; //右边第一个小于等于其1/2的数
int num[maxn];
int Bin(int top,int v) {
stk[0].v = 1e9;
stk[0].id = -1;
int l = 0,r = top;
int ans = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if(stk[mid].v >= v * 2) {
l = mid + 1;
ans = mid;
} else {
r = mid - 1;
}
}
return ans;
}
int Bin2(int top,int v) {
stk[0].v = -1e9;
stk[0].id = -1;
int l = 0,r = top;
int ans = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if(stk[mid].v * 2 <= v) {
l = mid + 1;
ans = mid;
} else {
r = mid - 1;
}
}
return ans;
}
class Solution {
public:
/**
*
* @param array int整型一维数组 array
* @param arrayLen int array数组长度
* @return long长整型
*/
long long MaxMin(int* array, int arrayLen) {
// write code here
int n = arrayLen;
for(int i = 0;i < n;i++) {
R2[i] = R3[i] = n;
a[i] = array[i];
}
int top = 0;
for(int i = n - 1;i >= 0;i--) { //单调递增队列,维护R3
while(top && stk[top].v > a[i]) {
top--;
}
// printf("%d \n",top);
R3[i] = stk[Bin2(top,a[i])].id;
if(R3[i] == -1) R3[i] = n;
stk[++top] = {i,a[i]};
}
top = 0;
for(int i = n - 1;i >= 0;i--) { //单调递减队列,维护R2
while(top && stk[top].v < a[i]) {
top--;
}
R2[i] = stk[Bin(top,a[i])].id;
if(R2[i] == -1) R2[i] = n;
stk[++top] = {i,a[i]};
}
ll ans = 0;
for(int i = 0;i < n;i++) {
ll r = min(R3[i],R2[i]);
b[i] = (n - 1 - r + 1);
}
for(int i = n - 2;i >= 0;i--) {
b[i] = max(b[i],b[i + 1]);
}
for(int i = 0;i < n;i++) {
ans += b[i];
}
return ans;
}
};