【StarryCoding P3】吸氧羊的StarryCoding之旅 题解(单调栈+贡献法)

186 阅读2分钟

吸氧羊的StarryCoding之旅

吸氧羊终于注册了一个StarryCoding账号!(她很开心)

但是吸氧羊忘记了它的密码,她想起你是计算机大师,于是就来请教你。

她虽然不记得密码了,但她记得一个数组,而这个密码就是这个数组中所有区间的最大值之和。

你赶快求出来吧,她太想进去玩了!

输入描述

第一行一个整数nn,表示数组aa的长度。(1n2×1051 \leq n \leq 2 \times 10^5

第二行nn个整数表示数组aa中的元素。(1ai1081 \leq a_i \leq 10^8

输出描述

一行一个整数表示结果。

输入样例

5
1 1 1 1 1

输出样例

15

解释

一共有15个区间,每个区间的最大值都是1,它们的和是15。


思路

首先,定义一些变量和数组,其中n是数组的长度,a是输入的数组,lr用于存储每个元素左侧和右侧的边界位置,stk是一个栈,用于辅助计算。接着,从标准输入流读取na的值。

然后,从左向右遍历数组。对于每个元素,如果栈不为空且栈顶元素小于当前元素,则弹出栈顶元素,直到栈顶元素不小于当前元素或栈为空。然后,如果栈不为空,将栈顶元素的位置+1作为当前元素的左边界,否则,左边界为1。最后,将当前元素的位置入栈。

清空栈后,从右向左遍历数组。对于每个元素,如果栈不为空且栈顶元素小于等于当前元素,则弹出栈顶元素,直到栈顶元素大于当前元素或栈为空。然后,如果栈不为空,将栈顶元素的位置-1作为当前元素的右边界,否则,右边界为n。最后,将当前元素的位置入栈。

最后,遍历数组,计算并累加每个元素的贡献值,即(i - l[i] + 1) * (r[i] - i + 1) * a[i]。其中,i - l[i] + 1是当前元素在左侧的区间数量,r[i] - i + 1是在右侧的区间数量,a[i]是当前元素的值。所以,每个元素的贡献值就是其值乘以其在所有区间中出现的次数。最后,将累加的结果输出。

注意

  1. 由于可能会出现重复数据,应该把a[i]作为区间[l,r]中最左边的最大值。在从左向右遍历数组时,若 a[stk.top()] < a[i] 则出栈。在从右向左遍历数组时,若 a[stk.top()] <= a[i] 则出栈。否则在计算贡献值时,重复数据会导致重复计算。
  2. 计算并累加每个元素的贡献值时,每个元素的贡献值就是其值乘以其在所有区间中出现的次数,别忘了乘上 a[i]

AC代码

#include <algorithm>
#include <iostream>
#include <stack>
#define AUTHOR "HEX9CF"
using namespace std;
using ll = long long;

const int N = 1e6 + 7;

int n;
ll a[N];
ll l[N], r[N];
stack<int> stk;

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}

	// 从左往右
	for (int i = 1; i <= n; i++) {
		while (stk.size() && a[stk.top()] < a[i]) {
			stk.pop();
		}
		if (stk.size()) {
			l[i] = stk.top() + 1;
		} else {
			l[i] = 1;
		}
		stk.push(i);
	}

	// 清空栈
	while (stk.size()) {
		stk.pop();
	}

	// 从右往左
	for (int i = n; i >= 1; i--) {
		while (stk.size() && a[stk.top()] <= a[i]) {
			stk.pop();
		}
		if (stk.size()) {
			r[i] = stk.top() - 1;
		} else {
			r[i] = n;
		}
		stk.push(i);
	}

	// 计算贡献
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += (i - l[i] + 1) * (r[i] - i + 1) * a[i];
	}
	cout << ans << "\n";
	return 0;
}