挑戰程序設計競賽 4.4常用技巧(二)

來自《挑戰程序設計競賽》

1.單調棧的應用

1.POJ2559

1.題目原文

Largest Rectangle in a Histogram
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 19669   Accepted: 6359

Description

A histogram is a polygon composed of a sequence of rectangles aligned at a common base line. The rectangles have equal widths but may have different heights. For example, the figure on the left shows the histogram that consists of rectangles with the heights 2, 1, 4, 5, 1, 3, 3, measured in units where 1 is the width of the rectangles: 

Usually, histograms are used to represent discrete distributions, e.g., the frequencies of characters in texts. Note that the order of the rectangles, i.e., their heights, is important. Calculate the area of the largest rectangle in a histogram that is aligned at the common base line, too. The figure on the right shows the largest aligned rectangle for the depicted histogram.

Input

The input contains several test cases. Each test case describes a histogram and starts with an integer n, denoting the number of rectangles it is composed of. You may assume that 1<=n<=100000. Then follow n integersh1,...,hn, where 0<=hi<=1000000000. These numbers denote the heights of the rectangles of the histogram in left-to-right order. The width of each rectangle is 1. A zero follows the input for the last test case.

Output

For each test case output on a single line the area of the largest rectangle in the specified histogram. Remember that this rectangle must be aligned at the common base line.

Sample Input

7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

Sample Output

8
4000

Hint

Huge input, scanf is recommended.

Source

2.解題思路

如果確定了長方形的左端點L和右端點R,那麼最大可能的高度就是min{h[i]|L<=i<R},就可以得到一個時間複雜度爲O(n^3)的算法。如果對計算區間最小值進行優化,就可以得到一個O(n^2)的算法,但是仍然超時。接下來想一下,如何高效的求解。
設面積最大的長方形的左端點是L,右端點是R,高度是H。如果h[L-1]>=H,那麼左端點就可以更新爲L-1,從而可以得到更大的長方形,與假設矛盾,所以h[L-1]<H,同理h[R]<H.H=min{h[i]|L<=i<R}。我們固定可以給出這樣的H的i並進行分析。L是滿足h[j-1]<h[i]的最大的j(j<=i),R是滿足h[j]<h[i]的最小的j(j>i)。如果能夠求出L[i],R[i],那麼最大面積就是max{h[i]*(R[i]-L[i])|0<=i<n}.
L[i]和R[i]可以利用單調棧快速求解。
我們先考慮求L的情況。
初始化一個棧,不斷增加i的值。對於每一個i,若棧頂元素大於等於h[i],就退棧,若棧爲空,則L爲0,否則L就是棧頂元素+1,然後把i加入棧中,維護單調棧。可以知道棧是單調的,而且棧頂元素最大。
求R的方法類似。
時間複雜度爲O(n)。

3.AC代碼

#include<algorithm>
#include<cctype>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<set>
#include<vector>
#include<cmath>
#include<bitset>
#include<stack>
#include<sstream>
#include<deque>
using namespace std;
#define INF 0x7fffffff
const int maxn=100005;

typedef long long ll;

int n;
int h[maxn];
int L[maxn],R[maxn];

void solve()
{
    //計算L
    stack<int> s;
    for(int i=0;i<n;i++){
        while(!s.empty()&&h[s.top()]>=h[i]){
            s.pop();
        }
        L[i]=(s.empty())?0:(s.top()+1);
        s.push(i);
    }

    while(!s.empty()){
        s.pop();
    }

    //計算R
    for(int i=n-1;i>=0;i--){
        while(!s.empty()&&h[s.top()]>=h[i]){
            s.pop();
        }
        R[i]=(s.empty())?n:s.top();
        s.push(i);
    }

    ll res=0;
    for(int i=0;i<n;i++){
        res=max(res,(ll)h[i]*(R[i]-L[i]));
    }

    printf("%lld\n",res);
}
int main()
{
    while(scanf("%d",&n)!=EOF&&n){
        for(int i=0;i<n;i++){
            scanf("%d",&h[i]);
        }
        solve();
    }
    return 0;
}

2.POJ3250

3.POJ2796

2.雙端隊列的應用

雙端隊列:一種支持在頭部和尾部插入刪除元素的數據結構。
由於雙端隊列中的元素始終保持單調性,也稱爲單調隊列

1.滑動最小值

1.題目原文

給定一個長度爲n的數組a[0],a[1],a[2],……a[n-1].求數組b[i]=min{a[i],a[i+1],……a[i+k-1]}(i=0,1,2,n-k)。
1<=k<=n<=10^6.

2.解題思路

可以利用RMQ在O(nlogn)時間內求解,但是利用要求的範圍大小是一定的,可以使用雙端隊列在O(n)時間內解決。

爲了計算b[0],可以把a[0],a[1],……a[k-1]依次加入到隊列中。在加入a[i]時,如果雙端隊列的末尾j滿足a[j]>=a[i],則把它刪除。直到雙端隊列爲空或者a[j]<a[i]爲止,把a[i]加入到雙端隊列隊尾。這種操作可以保持雙端隊列的單調性(單調遞增的)。因此最小的元素就是隊頭元素。等到k-1都加入到隊列中時,b[0]=a[j](隊頭元素).如果j=0,在之後的計算中,都不會再用到,就把它刪除。
接下來計算b[i]的情況一樣。
時間複雜度爲O(n)。

3.代碼

#include<algorithm>
#include<cctype>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<set>
#include<vector>
#include<cmath>
#include<bitset>
#include<stack>
#include<sstream>
#include<deque>
using namespace std;

const int maxn=1e6+5;
int n,k;
int a[maxn],b[maxn];

void solve()
{
    deque<int> deq;
    //雙端隊列是遞增的
    for(int i=0;i<n;i++){
        //在雙端隊列末尾加入i
        while(!deq.empty()&&a[deq.back()]>=a[i]){
            deq.pop_back();
        }
        deq.push_back(i);

        if(i-k+1>=0){
            b[i-k+1]=a[deq.front()];
            if(deq.front()==i-k+1){
                //從雙端隊列的隊頭刪除元素
                deq.pop_front();
            }
        }
    }
    for(int i=0;i<n-k;i++){
        cout<<b[i]<<" ";
    }
    cout<<b[n-k]<<endl;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
    }
    solve();
    return 0;
}



發佈了119 篇原創文章 · 獲贊 16 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章