Description
Bill is developing a new mathematical theory for human emotions. His recent investigations are dedicated to studying how good or bad days influent people's memories about some period of life.
A new idea Bill has recently developed assigns a non-negative integer value to each day of human life.
Bill calls this value the emotional value of the day. The greater the emotional value is, the better the daywas. Bill suggests that the value of some period of human life is proportional to the sum of the emotional values of the days in the given period, multiplied by the smallest emotional value of the day in it. This schema reflects that good on average period can be greatly spoiled by one very bad day.
Now Bill is planning to investigate his own life and find the period of his life that had the greatest value. Help him to do so.
https://blog.csdn.net/ACMore_Xiong/article/details/52326755
单调栈: 顾名思义就是在入栈时遵循单调原则,可以求出一个元素向左(或向右)所能扩展到的最大长度,并不是说在这一段区间内是单调的,而是保证在该区间内该元素一定是最大或最小;
单调栈主要是大家要自己枚举,需要找到每个元素最左能扩展到那 ,最优能扩展到那,当然最小的是你枚举的那个元素。
我们有如下的性质:
1. 如果当前元素大于前一元素,那么前一元素能扩展到当前元素,同时说明前面的数对当前元素来说是没有贡献的
2。如果当前元素等于前一元素,那么前一元素也能扩展到当前元素,同时说明前面的元素是可以被忽略的
3。如果当前元素小于前一个元素,那么前面至少有一个元素不能扩展到当前元素的位置,那么这些不能继续扩展的元素的存在显的没有什么意义了,不妨删除它。
我们得到两条结论:
1。一旦一个元素已经进入栈中那么这个元素向左扩展的位置就确定下来了.
2。一旦一个元素出栈被弹出栈,那么这个元素向右扩展的位置也确定下来了.
https://blog.csdn.net/u010885899/article/details/49148025
实际上这个题目就是要对每一个节点进行扩展,这样扩展的话,复杂度是O(n^2)。减少时间复杂度要用单调栈,单调栈处理的问题就是对每一个节点进行扩展的问题,这个题目要维护的是一个单调递减栈,即从栈顶元素到栈底元素,值是单调递减的,即栈顶元素的值始终是栈的最大值。然后每一个值有属于自己的区间,这个区间目的是为了记录之后的元素向前延伸的用处。
向后延伸就靠从1到n扫描元素,(维护单调递增栈)这样当扫描的元素大于栈顶元素时,直接入栈。
当扫描的元素等于栈顶元素时,不记录,只将区间延伸到后面。
当扫描的元素小于栈顶元素时,这时要计算栈内当前的值。因为扫描的元素时小于栈顶元素的,要求的是一个区间的最小值,所以栈内那些大于该元素的值你会发现没有用处了,只需要将它们的那些区间留下来就对了,这就是向前扩展。
拿题目的sample举例子:
3 1 6 4 5 2
一开始每一个数都有自己的区间:
3(1,1) 1(2,2) 6(3,3) 4(4,4) 5(5,5) 2(6,6) -1(7,7)后面加一个最小值,为了最后计算栈内元素使用。
先是3入栈。栈内元素 3(1,1)
1<3,首先计算一下栈内元素的值,记录下来。然后要把栈内大于1的全部弹出来,但是把它们的区间留下,栈内就变成了1(1,2)。实际上此时就会知道(1,2)这段区间之内的最小值是1。
6>1,直接入栈,栈内元素变为1(1,2),6(3,3)。
4<6,将6弹出,弹出之前计算值。然后栈内就变为1(1,2),4(3,4)。
5>4,直接入栈。栈内元素是1(1,2),4(3,4),5(5,5)。会发现因为5没有办法向前扩展了所以会知道5只能够在(5,5)的区间内最小,所以说站内元素是在自己区间的左端点与栈顶元素的右端点,这段区间之内满足着最小值的关系。1是在(1,5)这段区间内最小,4是在(3,5)这段区间内最小。这些值都会在碰到扫描的元素小于该元素时计算,记录下来,就是这样单调栈完成了对每一个元素进行左右扩展的目的。
2<5,2<4。要把5(5,5) 4(3,4)分别弹出,它们走之前要计算各自区间的值。
最后是-1,目的就是要将栈内所有元素弹出,计算每一个元素左右扩展的值。
http://tonyfang.is-programmer.com/posts/204255.html
Read full article from poj 2796 Feel Good(单调队列) - 记录成长的点点滴滴. . . . . - 博客频道 - CSDN.NET
Bill is developing a new mathematical theory for human emotions. His recent investigations are dedicated to studying how good or bad days influent people's memories about some period of life.
A new idea Bill has recently developed assigns a non-negative integer value to each day of human life.
Bill calls this value the emotional value of the day. The greater the emotional value is, the better the daywas. Bill suggests that the value of some period of human life is proportional to the sum of the emotional values of the days in the given period, multiplied by the smallest emotional value of the day in it. This schema reflects that good on average period can be greatly spoiled by one very bad day.
Now Bill is planning to investigate his own life and find the period of his life that had the greatest value. Help him to do so.
https://blog.csdn.net/ACMore_Xiong/article/details/52326755
单调栈: 顾名思义就是在入栈时遵循单调原则,可以求出一个元素向左(或向右)所能扩展到的最大长度,并不是说在这一段区间内是单调的,而是保证在该区间内该元素一定是最大或最小;
单调栈主要是大家要自己枚举,需要找到每个元素最左能扩展到那 ,最优能扩展到那,当然最小的是你枚举的那个元素。
我们有如下的性质:
1. 如果当前元素大于前一元素,那么前一元素能扩展到当前元素,同时说明前面的数对当前元素来说是没有贡献的
2。如果当前元素等于前一元素,那么前一元素也能扩展到当前元素,同时说明前面的元素是可以被忽略的
3。如果当前元素小于前一个元素,那么前面至少有一个元素不能扩展到当前元素的位置,那么这些不能继续扩展的元素的存在显的没有什么意义了,不妨删除它。
我们得到两条结论:
1。一旦一个元素已经进入栈中那么这个元素向左扩展的位置就确定下来了.
2。一旦一个元素出栈被弹出栈,那么这个元素向右扩展的位置也确定下来了.
https://blog.csdn.net/u010885899/article/details/49148025
实际上这个题目就是要对每一个节点进行扩展,这样扩展的话,复杂度是O(n^2)。减少时间复杂度要用单调栈,单调栈处理的问题就是对每一个节点进行扩展的问题,这个题目要维护的是一个单调递减栈,即从栈顶元素到栈底元素,值是单调递减的,即栈顶元素的值始终是栈的最大值。然后每一个值有属于自己的区间,这个区间目的是为了记录之后的元素向前延伸的用处。
向后延伸就靠从1到n扫描元素,(维护单调递增栈)这样当扫描的元素大于栈顶元素时,直接入栈。
当扫描的元素等于栈顶元素时,不记录,只将区间延伸到后面。
当扫描的元素小于栈顶元素时,这时要计算栈内当前的值。因为扫描的元素时小于栈顶元素的,要求的是一个区间的最小值,所以栈内那些大于该元素的值你会发现没有用处了,只需要将它们的那些区间留下来就对了,这就是向前扩展。
拿题目的sample举例子:
3 1 6 4 5 2
一开始每一个数都有自己的区间:
3(1,1) 1(2,2) 6(3,3) 4(4,4) 5(5,5) 2(6,6) -1(7,7)后面加一个最小值,为了最后计算栈内元素使用。
先是3入栈。栈内元素 3(1,1)
1<3,首先计算一下栈内元素的值,记录下来。然后要把栈内大于1的全部弹出来,但是把它们的区间留下,栈内就变成了1(1,2)。实际上此时就会知道(1,2)这段区间之内的最小值是1。
6>1,直接入栈,栈内元素变为1(1,2),6(3,3)。
4<6,将6弹出,弹出之前计算值。然后栈内就变为1(1,2),4(3,4)。
5>4,直接入栈。栈内元素是1(1,2),4(3,4),5(5,5)。会发现因为5没有办法向前扩展了所以会知道5只能够在(5,5)的区间内最小,所以说站内元素是在自己区间的左端点与栈顶元素的右端点,这段区间之内满足着最小值的关系。1是在(1,5)这段区间内最小,4是在(3,5)这段区间内最小。这些值都会在碰到扫描的元素小于该元素时计算,记录下来,就是这样单调栈完成了对每一个元素进行左右扩展的目的。
2<5,2<4。要把5(5,5) 4(3,4)分别弹出,它们走之前要计算各自区间的值。
最后是-1,目的就是要将栈内所有元素弹出,计算每一个元素左右扩展的值。
给你n个数的数组arr[n]。要你找到一个L和R是的。(arr[L]+.....+arr[R])*arr[k]的值最大。arr[k]为L->R的数的最小值。
思路:
很好的单调队列题。只要理解单调队列(升序)的特性。
1,如果刚入队完第j个元素。单调队列里剩下的元素(下标i)一定是[i,j]的最小值。因为如果不是最小的话就会被后面入队的值覆盖。
2,在j入队的过程中如果停在k(原数组下标)的后面说明arr[k]比arr[j]小。说明。arr[j]是除了arr[k]是[k,j]间最小的。即[k+1,j]最小的。
3,在j入队的过程中如果队里的i(原数组下标)出队。说明arr[i]是[i,j-1]间最小的。因为一加入j它就被出队了。说明它已不是最小。
Code from http://blog.himdd.com/archives/1588
Code from http://blog.himdd.com/archives/1588
11 | int main() |
12 | { |
13 | int n; |
14 | while (~ scanf ( "%d" ,&n)) |
15 | { |
16 | for ( int i=1;i<=n;i++) |
17 | { |
18 | scanf ( "%d" ,&elem[i]); |
19 | sum[i]=sum[i-1]+elem[i]; |
20 | } |
21 | elem[n+1]=-1; |
22 | long long ans=-1,tmp; |
23 | int top=0,l,r; |
24 | for ( int i=1;i<=n+1;i++) |
25 | { |
26 | while (top!=0&&elem[st[top]]>elem[i]) |
27 | { |
28 | tmp=elem[st[top]]*(sum[i-1]-sum[st[top-1]]); //栈顶元素是序列的最小元素 |
29 | if (ans<tmp) |
30 | { |
31 | ans=tmp; |
32 | l=st[top-1]+1; |
33 | r=i-1; |
34 | } |
35 | top--; |
36 | } |
37 | top++; |
38 | st[top]=i; |
39 | } |
40 | printf ( "%lld\n%d %d\n" ,ans,l,r); |
41 | } |
42 |
43 | } |
- const int INF=0x3f3f3f3f;
- const double eps=1e-8;
- const double PI=acos(-1.0);
- const int maxn=100010;
- typedef __int64 ll;
- int q[maxn];
- int le[maxn],ri[maxn],em[maxn];
- int head,tail;
- ll sum[maxn];
- int main()
- {
- int n,i,ans;
- ll mv,tt;
- sum[0]=0;
- em[0]=-1;
- while(~scanf("%d",&n))
- {
- em[n+1]=-1;//为了清空队列
- for(i=1;i<=n;i++)
- {
- scanf("%d",&em[i]);
- sum[i]=sum[i-1]+em[i];
- }
- head=tail=0;
- q[tail++]=0;
- for(i=1;i<=n+1;i++)
- {
- while(head<tail&&em[i]<em[q[tail-1]])
- {
- ri[q[tail-1]]=i-1;//被出队右边界就可以确定
- tail--;
- }
- le[i]=q[tail-1]+1;//可以确定左边界
- q[tail++]=i;
- }
- mv=-1;
- for(i=1;i<=n;i++)
- {
- tt=(sum[ri[i]]-sum[le[i]-1])*em[i];
- if(tt>mv)
- mv=tt,ans=i;
- }
- printf("%I64d\n%d %d\n",mv,le[ans],ri[ans]);
- }
- return 0;
- }
http://tonyfang.is-programmer.com/posts/204255.html
往两边单调栈求出第一个比当前小的数的位置。
scanf
(
"%d"
, &n);
for
(
int
i=1; i<=n; ++i) {
scanf
(
"%d"
, &a[i]);
s[i] = s[i-1] + a[i];
}
for
(
int
i=1; i<=n; ++i) {
if
(a[i] > st[stn]) {
st[++stn] = a[i];
last[i] = stw[stn-1];
stw[stn] = i;
}
else
{
while
(a[i] <= st[stn] && stn != 0) --stn;
if
(stn == 0) last[i] = 0;
else
last[i] = stw[stn];
st[++stn] = a[i];
stw[stn] = i;
}
}
stn = 0;
memset
(st, 0,
sizeof
(st));
memset
(stw, 0,
sizeof
(stw));
for
(
int
i=n; i>=1; --i) {
if
(a[i] > st[stn]) {
st[++stn] = a[i];
next[i] = stw[stn-1];
stw[stn] = i;
}
else
{
while
(a[i] <= st[stn] && stn != 0) --stn;
if
(stn == 0) next[i] = n+1;
else
next[i] = stw[stn];
st[++stn] = a[i];
stw[stn] = i;
}
}
for
(
int
i=1; i<=n; ++i) {
//printf("%d %d\n", last[i], next[i]);
long
long
t = (
long
long
)a[i] * (s[next[i]-1] - s[last[i]]);
if
(t>maxx) {
maxx=t;
l=last[i]+1;
r=next[i]-1;
}
}
cout << maxx << endl;