1. 程式人生 > >「日常訓練&知識學習」單調棧

「日常訓練&知識學習」單調棧

這幾天的知識學習比較多,因為時間不夠了。加油吧,為了夢想。
這裡寫幾條簡單的單調棧作為題解記錄,因為單調棧的用法很簡單,可是想到並轉化成用這個需要一些題目的積澱。
相關部落格參見:https://blog.csdn.net/wubaizhe/article/details/70136174

POJ 3250 Bad Hair Day

題意與分析

題意是這樣的:\(n\)個牛排成一列向右看,牛\(i\)能看到牛\(j\)的頭頂,當且僅當\(j\)在牛\(i\)的右邊並且牛\(i\)與牛\(j\)之間的所有牛均比牛\(i\)。設牛\(i\)能看到的牛數為\(C_i\),求\(\Sigma C_i\)
分析:注意到沒有?每個牛隻能向右看,看到的都是比自己矮的等價於每頭牛能被自己左邊且比自己大的牛看見

,這是啥?單調啊!維護一個單調棧,確定每頭牛左邊有幾頭牛比自己高的,然後求和即可。

程式碼

/*
 * Filename: poj3250.cpp
 * Date: 2018-11-13
 */

#include <iostream>
#include <cstring>
#include <stack>
#define INF 0x3f3f3f3f
#define PB push_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end()

#define QUICKIO                  \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr)

using namespace std;
typedef long long ll;
typedef int repType;
int
main()
{
    QUICKIO
    int n;
    while(cin>>n)
    {
        ll sum=0;
        stack<ll> s;
        ll h,t;
        cin>>h;
        s.push(h);
        rep(i,1,n-1)
        {
            cin>>t;
            while(!s.empty() && t>=s.top()) s.pop();
            sum+=s.size();
            s.push(t);
        }
        cout<<sum<<endl;
    }
    return 0;
}

POJ 2796 Feel Good

題意與分析

題意是這樣的:一個數組,對於某個區間,這個區間的和*這個區間中的最小值=這個區間的計算值。求這個陣列中的最大計算值,並任意輸出其的一個左右位置。
這題很容易想到我之前寫的一題:Codeforces Round #305 Div. 2 D——我們需要維護某個數分別是哪些區間的最小值。又注意到這個陣列是非負的,那麼肯定是越大越好。預先維護一個字首和,然後\(O(n)\)掃一遍完事了。
這兩題給我們一個啟示:單調棧可以維護區間最小值(目前看來是離線的情況下),通過維護左邊最右的比它小的值的和右邊最左的比它小的值。

程式碼

/*
 * Filename: poj2796.cpp
 * Date: 2018-11-14
 */

#include <iostream>
#include <cstring>
#include <stack>
#include <cstdio>

#define INF 0x3f3f3f3f
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end()

#define QUICKIO                  \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr)

using namespace std;
typedef long long ll;
typedef int repType;

const int MAXN=100005;
int n,l[MAXN],r[MAXN];
int arr[MAXN];
ll prefix[MAXN];
int main()
{
    while(scanf("%d",&n)==1)
    {
        prefix[0]=0;
        rep(i,1,n)
        {
            scanf("%d",&arr[i]);
            prefix[i]=prefix[i-1]+arr[i];
        }
        stack<int> s;
        rep(i,1,n)
        {
            while(!s.empty() && arr[s.top()]>=arr[i])
                s.pop();
            if(s.empty()) l[i]=0;
            else l[i]=s.top();
            s.push(i);
        }
        s=stack<int>();
        per(i,n,1)
        {
            while(!s.empty() && arr[s.top()]>=arr[i])
                s.pop();
            if(s.empty()) r[i]=n+1;
            else r[i]=s.top();
            s.push(i);
        }
        /*
        rep(i,1,n)
        {
            cout<<l[i]<<" "<<r[i]<<endl;
        }
        */
        ll ans=-1;
        int lp=1,rp=1;
        rep(i,1,n)
        {
            ll presum=prefix[r[i]-1]-prefix[l[i]];
            //cout<<arr[i]*presum<<endl;
            if(ans<arr[i]*presum)
            {
                ans=arr[i]*presum;
                lp=l[i]+1;
                rp=r[i]-1;
            }
        }
        printf("%lld\n%d %d\n", ans, lp, rp);
    }
    return 0;
}

HDU 1506 Largest Rectangle in a Histogram

題意與分析

題意其實就是要找到一個儘可能大的矩形來完全覆蓋這個矩形下的所有柱子,只能覆蓋柱子,不能留空。每一個柱子都要儘可能向左向右延伸,使得獲得最大的面積。
為什麼要把這幾題連起來寫呢?發現沒有,這幾題各種套路,看似不同,但是轉化後思路(聯動上面那個Codeforce題目)一模一樣!都是去維護一個區間最小值。這裡怎麼維護呢?考慮以自己為min,擴充套件的最大區域即可,然後掃一遍\(O(n)\)完事了(Codeforces題目的結論,區間長度為\(r_i-l_i-1\)),哈哈。

程式碼

/*
 * Filename: hdu1506.cpp
 * Date: 2018-11-14
 */


#include <iostream>
#include <cstring>
#include <stack>
#include <cstdio>

#define INF 0x3f3f3f3f
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end()

#define QUICKIO                  \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr)

using namespace std;
typedef long long ll;
typedef int repType;

const int MAXN=100005;
int n,l[MAXN],r[MAXN];
ll arr[MAXN];
int main()
{
    while(scanf("%d",&n)==1)
    {
        if(n==0) break;
        rep(i,1,n)
            scanf("%lld",&arr[i]);
        stack<int> s;
        rep(i,1,n)
        {
            while(!s.empty() && arr[s.top()]>=arr[i])
                s.pop();
            if(s.empty()) l[i]=0;
            else l[i]=s.top();
            s.push(i);
        }
        s=stack<int>();
        per(i,n,1)
        {
            while(!s.empty() && arr[s.top()]>=arr[i])
                s.pop();
            if(s.empty()) r[i]=n+1;
            else r[i]=s.top();
            s.push(i);
        }
        /*
        rep(i,1,n)
        {
            cout<<l[i]<<" "<<r[i]<<endl;
        }
        */
        ll ans=-1;
        rep(i,1,n)
        {
            ans=max(ans,arr[i]*(r[i]-l[i]-1));
        }
        printf("%lld\n", ans);
    }
    return 0;
}

HDU 5033 Building

題意與分析

題意:有一個人在到處是高樓大廈的地方擡頭仰望,假設所有高樓都在一條數軸上,給出了高樓的位置和高度,然後給出了人的位置(高度為0.....),問人能看到的陽光的最大角度是多少。
這題看起來和前面不太一樣了,角度而不是最小值區間,可是想不到吧,這也可以單調棧2333
怎麼寫呢?容易想到,決定一個人的視角的是他左右邊的建築物之間的斜率。如果不容易想到的話,可以參見 https://www.cnblogs.com/chen9510/p/7246889.html 的文章,裡面有幾幅圖片比較生動的表現了這個結論。
然後就是好玩的地方了:我們要在讀入所有的查詢後,將每個查詢點視為高度為0的樓,然後再通過比較兩棟相鄰樓頂連線的斜率大小維護一個單調棧。這樣寫就和上面的套路一樣一樣的了,哈哈。你會從程式碼中發現這一點。
(先睡了,醒了po)

程式碼

總結

只要你能轉化成離線的區間最值問題,那麼單調棧有可能能夠維護它(當然,問題需要滿足一定的性質——這就是我們需要乾的活了:直覺、轉化、or玄學)。