题目描述:
地上从左到右竖立着 n 块木板,从 1 到 n 依次编号,如下图所示。我们知道每块木板的高度,在第 n 块木板右侧竖立着一块高度无限大的木板,现对每块木板依次做如下的操作:对于第 i 块木板,我们从其右侧开始倒水,直到水的高度等于第 i 块木板的高度,倒入的水会淹没 ai 块木板(如果木板左右两侧水的高度大于等于木板高度即视为木板被淹没),求 n 次操作后,所有 ai 的和是多少。如图上所示,在第 4 块木板右侧倒水,可以淹没第 5 块和第 6 块一共 2 块木板,a4 = 2。
要使水不再往右延伸的话,那么必然要遇到一个比当前操作的木板更高的木板。
所以这道题转换成整型数组有n个元素,然后找到每个元素右边第一个大于该元素的数,并记录下标temp_id,那么第 i 块木板(下标为i)淹没木板的块数即为:
ai = temp_id - i - 1; //从图示中即可计算出ai的值
这道题可以双重for循环直接暴力遍历,时间复杂度为O(n^2)。但结合单调栈的性质:使用单调栈可以找到元素向左遍历第一个比他小的元素,也可以找到元素向左遍历第一个比他大的元素。我们可以用单调栈求解。我个人有两种思路。第一种,从左往右将木板节点压栈,遇到比栈顶木板高的木板就将当前栈顶木板出栈并计算淹没的木板数,如此循环直到栈顶木板高度比当前木板高或者栈为空,然后将此木板压栈。木板全都压栈完成后,栈内剩余的木板都是右侧没有比它们更高的木板的,所以一个个出栈并计算ai=n+1-temp_id-1(用最右边无限高的木板减)。第二种思路稍微麻烦一些,即从右往左将木板压栈,这样的话栈内每个元素的后面一个元素必然是它右侧第一个比它大的元素。具体细节看代码:
1 //从左往右解木板倒水 2 int main() { 3 int n,ans=0; 4 cin>>n; 5 Stack<Node> stack(n); 6 Node temp; 7 for(int i=1;i<=n;i++){ 8 cin>>temp.height; 9 temp.id=i; 10 //遇到了右侧第一个比栈顶元素大的元素,计算并出栈 11 while(!stack.empty()&&stack.top().height<=temp.height){ 12 ans=ans+i-stack.top().id-1; 13 stack.pop(); 14 } 15 stack.push(temp); 16 } 17 //现在栈中的木板右侧没有比它高的木板,用最右侧无限高的木板减 18 while(!stack.empty()){ 19 ans=ans+n+1-stack.top().id-1; 20 stack.pop(); 21 } 22 cout<<ans<<endl; 23 return 0; 24 } 25
26 //从右往左解木板倒水 27 int main(){ 28 int n,ans=0; 29 cin>>n; 30 Stack<Node> woods(n); 31 Stack<Node> stack(n); 32 Node temp; 33 for(int i=1;i<=n;i++){ 34 cin>>temp.height; 35 temp.id=i; 36 woods.push(temp); 37 } 38 //从右往左将木板压入栈 39 while(!woods.empty()){ 40 temp=woods.top(); 41 if(stack.empty()||temp.height<=stack.top().height){ 42 woods.pop(); 43 stack.push(temp); 44 } 45 else{ 46 Node t=stack.top(); 47 stack.pop(); 48 //为空表示节点t右侧没有比它高的木板,用无穷高木板减 49 if(stack.empty()){ 50 ans+=n+1-t.id-1; 51 } 52 //节点后面一个节点即为它右侧第一块比它高的木板 53 else{ 54 ans+=stack.top().id-t.id-1; 55 } 56 } 57 } 58 while(!stack.empty()){ 59 Node t=stack.top(); 60 stack.pop(); 61 if(stack.empty()){ 62 ans+=n+1-t.id-1; 63 } 64 else{ 65 ans+=stack.top().id-t.id-1; 66 } 67 } 68 cout<<ans<<endl; 69 return 0; 70 }