洛谷P1823 [COI2007] Patrik 音乐会的等待

本文介绍了一个基于单调栈的算法解决方案,用于解决 COI2007 比赛中的 Patrik 音乐会等待问题。该问题要求计算在音乐会上排队的人群中,有多少对人可以互相看见。文章详细解释了如何利用单调栈进行高效计算。

P1823 [COI2007] Patrik 音乐会的等待 - 洛谷

题目描述

n n n 个人正在排队进入一个音乐会。人们等得很无聊,于是他们开始转来转去,想在队伍里寻找自己的熟人。

队列中任意两个人 a a a b b b,如果他们是相邻或他们之间没有人比 a a a b b b 高,那么他们是可以互相看得见的。

写一个程序计算出有多少对人可以互相看见。

输入格式

输入的第一行包含一个整数 n n n,表示队伍中共有 n n n 个人。

接下来的 n n n 行中,每行包含一个整数,表示人的高度,以毫微米(等于 1 0 − 9 10^{-9} 109 米)为单位,这些高度分别表示队伍中人的身高。

输出格式

输出仅有一行,包含一个数 s s s,表示队伍中共有 s s s 对人可以互相看见。

样例 #1

样例输入 #1

7 
2 
4 
1 
2 
2 
5 
1

样例输出 #1

10

提示

数据规模与约定

对于全部的测试点,保证 1 ≤ 1\le 1 每个人的高度 < 2 31 < 2^{31} <231 1 ≤ n ≤ 5 × 1 0 5 1 \le n \le 5\times 10^5 1n5×105

题解

考察单调栈,但不是想象中的容易。事实上很不好做。

首先,题目要求统计人的「对数」,所以规定所有人只能往同一个方向看,不能左右都看,不然会重复统计。由于入栈时是按照输入顺序来的(这样最方便),所以队伍前面的人是已经站在准备入栈的新人前面的,所以所有人统一往队头看,即往栈底看最合适。

假设所有人的身高都是不同的。想象一下按顺序穿过这队人。观察任何一个排队等候的人。如果在A之后,我们遇到了一个更高的B,那么A肯定看不到B之后的任何人。因此,在通过这条队人时,我们可以维护一堆“开放子问题”,其中一个开放子问题是一个A已经在这队人中遇到的人,但A仍然有可能看到我们还没有遇到的人。

很明显,栈上的子问题是按照高度降序排序的(栈顶的子问题=最矮的人)。当我们遇到一个新人时,新人将入栈到栈顶。由于我们维护了栈的单调不增性,这个新人必然要成为栈中最矮的,所有他要往栈底看,看到栈中较矮的所有人,并将更矮的人从栈中移除(关闭他们的子问题)。如果栈在此之后不是空的,则该新人还可以看到栈上剩余的第一个人,显然这个人比新人高,因为他没有被移除。然后这个新人入栈。

那么什么时候统计答案ans?在新人准备入栈时,他从栈顶往栈底看,让所有比他矮的人出栈,这些比他矮的人他都看见了,所以要统计在答案中,所以在出栈时要统计ans;在新人入栈后,他要看一下栈底方向还有没有一位“高人”,如果有,则这是他能看到的最后一个人,所以出栈后也要统计ans

前面是假设所有人身高不同,因为身高相同的人虽然高度相同,但是属于不同的人,他们能看到的人数不一样。

一种好的解决办法是将身高相同且相邻的人打包成1个人,这个人有一个属性,表示他是由几个这样的人打包来的。所以可以用pair<身高, 身高相同且相邻的人数>来存储每个人。这样,在出栈时的统计就是ans每次累加pair的第二个分量。

#include <iostream>
#include <stack>
using namespace std;
int n;
stack<pair<int, int>> stk;  // first是身高,second是截止到目前连续身高相同的人数
long long ans;
int main() {
    scanf("%d", &n);
    while (n--) {
        int x;
        cin >> x;
        pair<int, int> p(x, 1);
        // 维护一个单调不增栈
        while (!stk.empty() && stk.top().first <= x) {
            if (stk.top().first == x) {
                p.second += stk.top().second;  // 累计连续的(能相互看到的)身高相同的人
            }
            ans += stk.top().second;  // 当前x能看到x左边那个人,多个连续的高度相同的人能互相看到
            stk.pop(); // 将左边更矮(身高相同的人也出栈,因为记录了连续人数,待会回入栈,相当于打包成1个人)的人出栈
        }
        if (!stk.empty())
            ans++;  // 当前x能看到x左边那个人,这个人比x高
        stk.emplace(p); //将x入栈
    }
    printf("%lld", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值