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[i−1],...,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;
}