可见的山峰对数量(进阶)
题目描述
一个不含有负数的数组可以代表一圈环形山,每个位置的值代表山的高度。比如,{3,1,2,4,5},{4,5,3,1,2}或{1,2,4,5,3}都代表同样结构的环形山。3->1->2->4->5->3 方向叫作 next 方向(逆时针),3->5->4->2->1->3 方向叫作 last 方向(顺时针)。
山峰 A 和 山峰 B 能够相互看见的条件为:
-
如果 A 和 B 是同一座山,认为不能相互看见。
-
如果 A 和 B 是不同的山,并且在环中相邻,认为可以相互看见。
-
如果 A 和 B 是不同的山,并且在环中不相邻,假设两座山高度的最小值为 min。如果 A 通过 next 方向到 B 的途中没有高度比 min 大的山峰,或者 A 通过 last 方向到 B 的途中没有高度比 min 大的山峰,认为 A 和 B 可以相互看见。
问题如下:
给定一个含有负数可能有重复值的数组 arr,请问有多少对山峰能够相互看见?
输入描述:
第一行给出一个整数 n,表示山峰的数量。
以下一行 n 个整数表示各个山峰的高度。
输出描述:
输出一行表示答案。
示例1
输入
5
3 1 2 4 5
输出
7
备注:
1
≤
n
≤
1000000
1 \le n \le 1000000
1≤n≤1000000
−
1000000
≤
a
r
r
i
≤
1000000
-1000000 \le arr_i \le 1000000
−1000000≤arri≤1000000
题解:
还是使用 可见的山峰对数量 中的 “小找大” 思想。由于有重复值,每个点的贡献不确定,需要使用单调栈,因为单调栈能找到离当前山最近且比它大的山峰。
单调栈中保存的是一个元组(value, times),即值和次数,满足从栈顶到栈底 value 严格单调递增。
整个过程分为遍历阶段和清算阶段:
-
遍历阶段
某个记录 (x, k) 从栈中弹出,产生可见山峰的数量为:$2*k + C(2, k) $ 对。
- 若 k == 1,则 C ( 2 , k ) = 0 C(2, k)=0 C(2,k)=0
- 若 k > 1,则 C ( 2 , k ) = k ∗ ( k − 1 ) / 2 C(2, k) = k*(k - 1) / 2 C(2,k)=k∗(k−1)/2
-
清算阶段
某个记录 (x,k) 从栈中弹出, 产生可见山峰对的数量为:
- 若栈中元素个数大于 2,则此时产生 2 ∗ k + C ( 2 , k ) 2*k + C(2, k) 2∗k+C(2,k) 对;
- 若栈中元素个数等于 2,若栈底元素出现 1 次,此时产生 k + C ( 2 , k ) k + C(2, k) k+C(2,k) 对,否则产生 2 ∗ k + C ( 2 , k ) 2*k + C(2, k) 2∗k+C(2,k) 对。
- 若栈中元素个数等于 1,此时产生 C ( 2 , k ) C(2, k) C(2,k) 对。
详细解释看书上描述吧23333。
代码:
#include <cstdio>
#include <stack>
using namespace std;
const int N = 1000000;
typedef pair<int, int> PII;
int n;
int a[N];
int main(void) {
scanf("%d", &n);
int mx_idx = 0;
for ( int i = 0; i < n; ++i ) {
scanf("%d", a + i);
if ( a[mx_idx] < a[i] ) mx_idx = i;
}
stack<PII> stk;
int i = mx_idx;
int ret = 0;
for ( ; i == mx_idx || i % n != mx_idx; ++i ) {
int k = i % n;
while ( stk.size() && stk.top().first < a[k] ) {
auto t = stk.top();
stk.pop();
int times = t.second;
ret += ((times * ( times - 1 )) >> 1) + ( times << 1 );
}
if ( stk.size() && stk.top().first == a[k] ) stk.top().second++;
else stk.push( {a[k], 1} );
}
while ( stk.size() ) {
auto t = stk.top();
stk.pop();
int times = t.second;
ret += (times * ( times - 1 )) >> 1;
if ( stk.size() > 1 || ( stk.size() == 1 && stk.top().second > 1) )
ret += times;
if ( stk.size() ) ret += times;
}
printf("%d\n", ret);
return 0;
}