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

这篇文章介绍了一种算法,通过使用栈来解决吸氧羊在StarryCoding网站上找回密码的问题。给定一个数组,要求求解数组中所有区间的最大值之和,其中每个区间的边界由数组元素决定。算法利用左右两次遍历和栈的操作找到每个元素在区间内的贡献值。
摘要由CSDN通过智能技术生成

吸氧羊的StarryCoding之旅

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

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

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

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

输入描述

第一行一个整数 n n n,表示数组 a a a的长度。( 1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1n2×105

第二行 n n n个整数表示数组 a a a中的元素。( 1 ≤ a i ≤ 1 0 8 1 \leq a_i \leq 10^8 1ai108

输出描述

一行一个整数表示结果。

输入样例

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值