UVa1619 感觉很好

UVa1619 感觉很好 解题报告

题目链接
  这题使用单调栈,可以在 O ( n ) O(n) O(n)时间内解决,单调栈还是不熟练,总结一下希望能提高吧。

题目分析

  题目的意思很简单,实际就是给定一个数组 A,在其中找出一个子序列 A [ i ] . . . A [ j ] A[i]...A[j] A[i]...A[j],使其满足 s u m ( A [ i ] + A [ i + 1 ] + . . . + A [ j ] ) ∗ m i n ( A [ i ] , A [ i − 1 ] , . . . , A [ j ] ) sum(A[i]+A[i+1]+...+A[j])*min(A[i],A[i-1],...,A[j]) sum(A[i]+A[i+1]+...+A[j])min(A[i],A[i1],...,A[j])为最大值。根据题目的要求,至少要设计出 O ( n log ⁡ n ) O(n\log n) O(nlogn)的算法,当时我有以下几种思路:

  • 枚举起点,然后快速找到终点,不过这题好像没法在 O ( 1 ) 、 O ( log ⁡ n ) O(1)、O(\log n) O(1)O(logn)时间内快速找到终点
  • 枚举子序列的长度,然后用滑动窗口判断,不过好像没法二分枚举,肯定会超时
  • 枚举最小值的可能位置,然后在 O ( 1 ) 、 O ( log ⁡ n ) O(1)、O(\log n) O(1)O(logn)时间内算出,以当前枚举位置为最小值,满足条件的子序列最大值

  用贪心的思想,当最小值一定时,上述式子的长度越长,那么最后算出的值越大,所以我们只要找出,以当前枚举位置为最小值,向左向右最多能延伸多长即可。可以通过预处理出Left[i]、right[i],表示i位置为最小值时,向左向右最多能延伸的位置。

算法设计

  现在的唯一问题就是,如何在 O ( n ) O(n) O(n)时间内得到当前数字向左向右最大的延伸位置?
  不难想到,可以用单调栈+滑动窗口来实现,这样在统计的过程中,每个元素至多入队出队一次,时间复杂度为 O ( n ) O(n) O(n)。具体思想就是,假设新加入的元素为 a a a 那么,将栈中所有 大于等于 a a a 的元素都出栈。出栈完毕后,栈顶元素位置即为当前元素能延伸到的最大位置,最后将当前元素入栈。
  说明如下:因为当栈中的一个元素大于 a a a 时,它所能延伸到的位置,那么 a a a 也一定能延伸到,而如果在栈中碰到一个小于 a a a 的元素时,那么这个栈中元素的位置就是 a a a 能延伸到的最大位置。出栈条件是 ≥ \geq 是因为需要尽量多的延伸,当相等时也要尽量往外延伸。

  总结一下单调栈的使用:

  • 首先确定入栈的元素是什么(是数组中的元素,还是数组的下标)
  • 确定单调栈中出栈的条件(大于还是小于,是否有等于)
  • 新加入的元素一定要在最后入栈
  • 注意栈为空的处理

完整代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
const int maxn = 100000 + 1000;
int A[maxn];
int n;
long long sum[maxn];
int stack[maxn];
int top;
int L[maxn], R[maxn];

int kcase = 0;

void solve()
{
	memset(sum, 0, sizeof(sum));
	//计算前缀和
	for (int i = 1; i <= n; i++)
		sum[i] = sum[i - 1] +(long long) A[i];

	//利用单调栈预处理出 left[i]  表示第i个数字最左能延伸到的位置
	top = 0;
	memset(stack, 0, sizeof(stack));
	memset(L, 0, sizeof(L));
	memset(R, 0, sizeof(R));

	for (int i = 1; i <= n; i++)
	{
		//栈非空,而且栈顶元素大于当前元素,说明栈顶元素能延伸到的位置,当前元素一定能延伸到
		while (top > 0 && A[stack[top - 1]] >= A[i]) top--;
		
		L[i] = top == 0 ? 1 : (stack[top - 1] + 1);

		//当前元素入栈
		stack[top++] = i;
	}

	top = 0;
	memset(stack, 0, sizeof(stack));
	for (int i = n; i >= 1; i--)
	{
		while (top > 0 && A[stack[top - 1]] >= A[i]) top--;

		R[i] = top == 0 ? n : (stack[top - 1] - 1);

		stack[top++] = i;
	}

	int left=1, right=1;
	long long  max_val = 0;

	for (int i = 1; i <= n; i++)
	{
		long long  temp_val = (sum[R[i]] - sum[L[i] - 1])*(long long )A[i];

		if (temp_val > max_val ||( temp_val == max_val && (right - left) > (R[i] - L[i])))
		{
			max_val = temp_val;
			left = L[i];
			right = R[i];
		}
	}

	printf("%lld\n%d %d\n", max_val, left, right);
}

int main()
{
	freopen("C:\\Users\\lenovo\\Desktop\\test\\in.txt", "r", stdin);
	freopen("C:\\Users\\lenovo\\Desktop\\test\\out.txt", "w", stdout);
	while (scanf("%d", &n) == 1 && n)
	{
		if (kcase++) printf("\n");
		for (int i = 1; i <= n; i++)
			scanf("%d", &A[i]);
		solve();
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值