单调栈 && 洛谷 P2866 [USACO06NOV]糟糕的一天Bad Hair Day(单调栈)
传送门
这是一道典型的单调栈。
题意理解
先来理解一下题意(原文翻译得有点问题)。
其实就是求对于序列中的每一个数i,求出i到它右边第一个大于i的数之间的数字个数c[i]。最后求出和。
首先可以暴力求解,时间复杂度o(n^2)显然TLE。
然后就是用单调栈来做。
单调栈
单调栈就是维护一个栈,使得栈中的元素是单调的(递增/递减)。
假设是递减——对于每一个新来的元素,把栈顶大于这个元素的每一个数字全部弹出,最后把这个元素加进去。
(如果栈为空,直接加入)
单调栈有什么用呢?
- 单调递增栈能以o(n)时间复杂度求出左右两边第一个比它比它小的元素。
- 进栈元素能入栈的时候,此时栈顶元素一定是第一个左边第一个比进栈元素小的元素。
- 栈内元素出栈的时候,此时进栈元素一定是第一个右边第一个比栈顶元素小的元素。
- 单调递减栈能以o(n)时间复杂度分别求左右两边第一个比它大的元素。
- 进栈元素能入栈的时候,此时栈顶元素一定是第一个左边第一个比进栈元素大的元素。
- 栈内元素出栈的时候,此时进栈元素一定是第一个右边第一个比栈顶元素大的元素。
解题思路
因为这道题求的是最大的元素,所以用单调递减栈。
第一种方法就是对于每一个元素,求出其右边第一个大于它的元素,最后作差求和。
第二种更为简单却难以思考的方法是对于每一个即将进栈的数,ans就加上此时(该元素还未进栈)栈内的元素个数。
为什么呢?
此时对于栈内的所有元素,一定是呈单调递减的,所以这个即将进栈的元素就能被栈内的元素看到,所以答案加上栈内的元素个数。
注意事项
- 用long long,否则会爆。
- 注意读题,出栈的条件是<=而不是<。
- while循环&&的s.size()条件必须放在左边。
AC代码
1 #include<iostream> 2 #include<stack> 3 #include<cstdio> 4 using namespace std; 5 long long ans; 6 stack<int>s; 7 int n,now; 8 int main(){ 9 cin>>n; 10 for(int i=1;i<=n;++i) 11 { 12 scanf("%d",&now); 13 while(s.size()&&s.top()<=now) s.pop(); 14 ans+=s.size(); 15 s.push(now); 16 } 17 cout<<ans; 18 return 0; 19 }