單調棧應用
顧名思義 單調棧就是帶有單調性質的棧
模擬單調棧
陣列: 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
輸入描述
第一行包含一個正整數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);
}
}
}
單調遞增棧
使用單調棧的思路不同之處是從左往右遍歷的,找到當前山峰最左側可以相互監視的山峰:當入棧元素大於棧頂元素時,出棧,這時出棧的元素都是能夠和入棧元素相互監視的