1. 程式人生 > 其它 >單調棧應用

單調棧應用

顧名思義 單調棧就是帶有單調性質的棧

模擬單調棧

陣列: 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)座山上,當且僅當兩人所在的山比兩人之間所有的山都高img時,這兩個哨兵可以相互監視,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);
        }
    }
}

單調遞增棧

使用單調棧的思路不同之處是從左往右遍歷的,找到當前山峰最左側可以相互監視的山峰:當入棧元素大於棧頂元素時,出棧,這時出棧的元素都是能夠和入棧元素相互監視的