可见的山峰对数量(进阶)

可见的山峰对数量(进阶)

题目描述

一个不含有负数的数组可以代表一圈环形山,每个位置的值代表山的高度。比如,{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 能够相互看见的条件为:

  1. 如果 A 和 B 是同一座山,认为不能相互看见。

  2. 如果 A 和 B 是不同的山,并且在环中相邻,认为可以相互看见。

  3. 如果 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 1n1000000
− 1000000 ≤ a r r i ≤ 1000000 -1000000 \le arr_i \le 1000000 1000000arri1000000


题解:

还是使用 可见的山峰对数量 中的 “小找大” 思想。由于有重复值,每个点的贡献不确定,需要使用单调栈,因为单调栈能找到离当前山最近且比它大的山峰。

单调栈中保存的是一个元组(value, times),即值和次数,满足从栈顶到栈底 value 严格单调递增。

整个过程分为遍历阶段和清算阶段:

  • 遍历阶段

    某个记录 (x, k) 从栈中弹出,产生可见山峰的数量为:$2*k + C(2, k) $ 对。

    1. 若 k == 1,则 C ( 2 , k ) = 0 C(2, k)=0 C(2,k)=0
    2. 若 k > 1,则 C ( 2 , k ) = k ∗ ( k − 1 ) / 2 C(2, k) = k*(k - 1) / 2 C(2,k)=k(k1)/2
  • 清算阶段

    某个记录 (x,k) 从栈中弹出, 产生可见山峰对的数量为:

    1. 若栈中元素个数大于 2,则此时产生 2 ∗ k + C ( 2 , k ) 2*k + C(2, k) 2k+C(2,k) 对;
    2. 若栈中元素个数等于 2,若栈底元素出现 1 次,此时产生 k + C ( 2 , k ) k + C(2, k) k+C(2,k) 对,否则产生 2 ∗ k + C ( 2 , k ) 2*k + C(2, k) 2k+C(2,k) 对。
    3. 若栈中元素个数等于 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值