顾名思义 单调栈就是带有单调性质的栈
模拟单调栈
数组: 4,5,2,6,3
单调递增栈(栈顶小于栈底,元素入栈时,若符合单调性,则入栈,若不符合单调性,栈顶元素出栈,直到符合单调性才入栈):
-
栈为空 ,4入栈 ,栈中为:4
-
5大于栈顶(4),4出栈,5入栈,栈中为:5
-
2小于栈顶(5),2入栈,栈中为:5,2
-
6大于栈顶(2),2出栈,栈中为:5,6仍然大于栈顶(5),5出栈,6入栈,栈中为:6【结束】
例题
牛客:14666 最优屏障
链接:https://ac.nowcoder.com/acm/problem/14666
来源:牛客网
M国的地势高低不平,现给出一个数组代表此国家某纬度上均匀分布的N座山的海拔高度Hi,已知每座山的山顶上都有一座哨塔,若两个哨兵分别位于第i、j(i<j)座山上,当且仅当两人所在的山比两人之间所有的山都高
时,这两个哨兵可以相互监视,M国的防守能力大小为相互监视的哨兵对数。H国早已对M国虎视眈眈,H国的皇帝希望黑魔法师们可以在M国的某两座山之间放置一块巨大的屏障,M国的哨兵不可通过该屏障互相监视。皇帝想让你告诉他最优的屏障放置位置,你是皇帝手下最信任的军师,现在需要你帮助皇帝计算最优的屏障放置位置和最大的防守力减少量。
输入描述
第一行包含一个正整数T(T≤20)。
对于每组数据,第一行包含一个正整数n(2≤n≤50000)。
接下来n个不同的正整数,H1,H2,H3,…,Hn(0≤Hi≤109)分别代表横截面上每座山的海拔高度。
(读入数据比较大,建议使用scanf而不要使用cin读入)
对于60%的数据,n≤500
对于80%的数据,n≤5000
对于100%的数据,n≤50000
输出描述
每组数据输出一行形如“Case #N: X C”,N代表当前是第N组数据(从1开始),X代表屏障放置在第X座山前可使M国的防守能力下降最多, 此时减少量为C。若有多种方案使得减少量为C,那么输出最小的X对应的方案。
示例1
输入
2
3
2 1 3
5
4 5 2 6 3
输出
Case #1: 2 2
Case #2: 3 2
思路
这个题本来不是用单调栈写的,但思路相似,所以不再写单调栈的代码
这个题先给出 n 个山峰的值,我们用 h[] 数组存储。要求出屏障放在哪里使减少的防守力最多,那我们就要会有条理的求出样例的防守力(先去模拟才能发现规律)。
我们知道两座山峰可以相互监视的条件,模拟时可以发现一个规律:当我们求 i 的右侧 i+1,i+2…… 山峰是否可以和i 相互监视的时候,若遇到第一个 h[ i ]<= h[ i+k ] 的情况,那么 i+k 就是 i 可以互相监视的最右侧的山峰(理解互相监视的条件式子 两个山峰只有高于它们之间的山峰时,才能互相监视)
但是数据很大 我们不可能暴力去做,如果我们从右往左遍历 i 的话,我们如果求出了 i 的最右侧山峰,那么对于第 i-1 个山峰就可以首先判断第 i 个山峰是否是 i-1 的最右侧山峰,如果不是,我们如果可以直接判断第 i 个山峰的最右侧山峰是否高于第 i-1 个山峰(因为 第i个山峰和它本身的最右侧山峰之间的山峰一定小于第 i 个山峰,它们不能作为 i-1 的最右侧山峰),以此类推,递进寻找最右侧山峰。
在寻找到最右侧山峰的时候,可以用数组标记这对互相监视的山峰,最后求前缀和(相当于两个山峰看成两点,连成一线,下标和对应的和就是有多少条线从这里经过)最后求出最大值和下标即可
撸代码
import java.io.*;
import java.util.*;
/*牛客14666:最优屏障*/
public class Main {
/*优化的输入*/
static StreamTokenizer cin=new StreamTokenizer(new BufferedReader(new InputStreamReader(new BufferedInputStream(System.in))));
static PrintWriter cout=new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(System.out)));
public static int readInt(){
int ans=0;
try{
cin.nextToken();
ans=(int)cin.nval;
}
catch(Exception e){}
finally{
return ans;
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int t;
t=readInt();
int Case=1;
while (0!=t--){
int n;
n = readInt();
int[] h=new int[n+2];
for (int i=1;i<=n;i++){
h[i] = readInt();/* 输入山的高度 1~n */
}
/* 找到点i 之后可以相互监视的点j(这里进行优化) 记录上空有多少条线 */
int[] fa = new int[n+2];/*记录点 i 可以监视的最后一个点的位置;方便点i-1 向上遍历 */
for (int i = 0; i < n+2; i++) {
fa[i]=-1;
}
int[] line = new int[n+2];/*互相监视的两点连成一线 记录上空有多少条线*/
for (int i = n-1; i >=1 ; i--) {//当前山峰 i
int j=i+1; // 后面的山峰 j
while (true&&j>=1&&j<=n){
if(h[i]<=h[j]){/* i j 可以互相监视 且到 j 为止后面的不能再监视*/
fa[i]=j;
line[i+1]++;/*记录这条路线的起点终点*/
line[j+1]--;
break;
}
line[i+1]++;
line[j+1]--;
j=fa[j];
}
}
int maxLine=0,idx=0;
for (int i = 2; i <=n ; i++) {
line[i]+=line[i-1];
if (maxLine<line[i]){
maxLine=line[i];
idx=i;
}
}
System.out.println("Case #"+(Case++)+": "+idx+" "+maxLine);
}
}
}
单调递增栈
使用单调栈的思路不同之处是从左往右遍历的,找到当前山峰最左侧可以相互监视的山峰:当入栈元素大于栈顶元素时,出栈,这时出栈的元素都是能够和入栈元素相互监视的

1517

被折叠的 条评论
为什么被折叠?



