一道題--4遍A!:Histogram(LightOJ 1083)

題目:Histogram

題目位置:

LightOJ 1083

題意:

就是讓你找到一個矩形之中的最大子矩陣!
就是這樣的圖片!

NO.1 : 單調棧的想法:

核心

對於每一個矩形,如果暴力的想法就是找到這個矩形的高度往左往右最多能夠到達哪裏.
但是這樣的複雜度就是N^2了,這並不夠好!所以我們可以利用這個思想:
我們會發現,一個小的矩形就會”統治”高度大的矩形!於是……

創建一個stack 來儲存中間一部分的矩形!

這裏寫圖片描述

    其實可以想象:這些省略號之中的矩形是高度都大於 左右兩邊 入棧的高度的!
因爲我們記錄的是高度會發生改變的矩形,而之中被省略的矩形只是高度大,而左右的矩形會"統治"這些高的!
在每一次的新元素進來,就是這樣的......

這裏寫圖片描述
於是就是在棧裏面的高度較爲低的矩形全部被”丟”出去!
同時在開始計算ans的值 : 就是

丟出去的矩形的高度*(加入元素的位置-前一個(就是丟出矩形的元素的前一個)棧元素的位置的前面-1)

代碼:

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define P std::pair<int,int>
#define N 100005
#define INF 0x3f3f3f3f
std::stack<P> fin;
int high[N];
int ans=-INF,n;
inline void get( )
{
    fin.push(std::make_pair(-1,0));
    for(int i=1; i<=n+1; i++)
    {
        P p=fin.top();
        while(high[p.second]>high[i]&&!fin.empty())
        {
            ans=std::max(ans,abs(i-p.first-1)*high[p.second]);
            fin.pop();
            if(!fin.empty())
                p=fin.top();
        }
        fin.push(std::make_pair(p.second,i));
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    for(int loc=1;loc<=T;loc++)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
            scanf("%d",&high[i]);
            high[n+1]=0;
        get();
        printf("Case %d: %d\n",loc,ans);
        ans=-INF;
    }
    return 0;
}

NO.2:利用KMP思想:

其實這個代碼十分的好懂,於是就少說一點…

核心:

left[i]=i;
   while(left[i]-1>=0&&value[left[i]-1]>=value[i])
left[i]=left[left[i]-1];

代碼:

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define N 100005
#define INF 0x3f3f3f3f
int lft[N],rght[N],value[N];
int ans=-INF,n,T;
inline int read(){
    int x=0;char c=getchar();bool flag=0;
    while(c<'0'||c>'9')  {if(c=='-')flag=1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0'; c=getchar();}
    flag?-x:x;
    return x;
}
int main()
{
    T=read();
    for(int loc=1;loc<=T;loc++)
    {
        n=read();
        for(int i=1; i<=n; i++)
            value[i]=read();
        for(int i=1; i<=n; i++) { lft[i]=i;while(lft[i]-1>=0&&value[lft[i]-1]>=value[i]) lft[i]=lft[lft[i]-1]; }
        for(int i=n; i>=1; i--) { rght[i]=i;while(rght[i]+1<=n&&value[rght[i]+1]>=value[i]) rght[i]=rght[rght[i]+1]; }
        for(int i=1; i<=n; i++) ans=std::max(ans,(rght[i]-lft[i]+1)*value[i]);
        printf("Case %d: %d\n",loc,ans);
        ans=-INF;
    }
    return 0;
}

NO.3 RMQ的想法

核心:

表示這個就是一個倍增類DP的作法,比如dp[i][j] 表示 從 i 開始 向後邊 拓展 2^j 次的結果 ,表示這個區間的最小值的記錄,用一個 pair 類型來記錄 值 和 位置!
比如這樣:
這裏寫圖片描述
這是利用分治的思想 :

舉個”栗子”
比如是 去求 1~10 的區間的最小值,我只要知道1~5 and 6~10 的區間的最小值,
在來取2個最小值的最小值就好了!
於是十分的好打 : NlogN 預處理

但是,表示自己打code 打萎了!

len[i] := 表示 2^j <= i 的時候 : 比如 len[4]=2,len[5]=2,len[8]=3,len[9]=3;
所以貼出來 一個 錯誤的代碼,只是提供一個思路!

錯誤代碼:

#include <bits/stdc++.h>
using namespace std;
enum number { N=30005,INF=0x3f3f3f3f};
int n,T,a[N],len[N];
int ans=-INF;
struct f
{
    int val,loc;
} come[N][50],back[N][50];
int pow_(int x,int y)
{
    int tot=1;
    while(x)
    {
        if(y&1) tot*= x;
        x*=x;
        y>>=1;
    }
    return tot;
}
void init()
{
    for(int i=1; i<=n; i++) come[i][0].val=a[i],back[i][0].val=a[i];
    for(int i=1; i<=n; i++) come[i][0].loc=i,back[i][0].loc=i;
    for(int j=1; pow(2,j)<=n; j++)
        for(int i=1; i+pow(2,j)-1<=n; i++)
        {
            int dis=i+pow(2,j-1);
            come[i][j].val=min(come[i][j-1].val,come[dis][j-1].val);
            if(come[i][j-1].val<come[dis][j-1].val) come[i][j].loc=come[i][j-1].loc;
            else come[i][j].loc=come[dis][j-1].loc;
        }
    for(int j=1; pow(2,j)<=n; j++)
        for(int i=n; i-pow(2,j)>=0; i--)
        {
            int dis=i-pow(2,j-1);
            back[i][j].val=min(back[i][j-1].val,back[dis][j-1].val);
            if(back[i][j-1].val<back[dis][j-1].val) back[i][j].loc=back[i][j-1].loc;
            else back[i][j].loc=back[dis][j-1].loc;
        }
    int l=0;
    for(int i=1; i<=n; i++)
    {
        if((1<<(l+1))<i) l++;
        len[i]=l;
    }
}
f solve(int l,int r)
{
    if(l>r) swap(l,r);
    int le=len[r-l+1];
    if(come[l][le].val<back[r][le].val) return come[l][le];
    else return back[r][le];
}
void Find(int left,int right)
{
    f now=solve(left,right);
    ans=max(ans, now.val*(right-left+1));
    if(now.loc-1>=left)
    Find(left,now.loc-1);
    if(now.loc+1<=right)
    Find(now.loc+1,right);
}
int main()
{
    scanf("%d",&T);
    for(int loc=1; loc<=T; loc++)
    {
        memset(come,INF,sizeof(come));
        memset(back,INF,sizeof(back));
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        init();
        Find(1,n);
        printf("Case %d: %d\n",loc,ans);
    }
    return 0;
}

正確代碼

主要是TLE的過程就是因爲每一次 枚舉 len[i] 爲多少的時候 , 在每一次都 預處理了一下! 但是事實上 就是隻要處理 到 30000就好了!
所以拉到外面 了 之後 在預處理一下就是OK了!

#include <bits/stdc++.h>
using namespace std;
enum number { N=30005,INF=0x3f3f3f3f};
int n,T,a[N],len[N];
int ans=-INF;
struct f
{
    int val,loc;
} come[N][50],back[N][50];
void init()
{
    for(int i=1; i<=n; i++) come[i][0].val=a[i],come[i][0].loc=i;
    for(int j=1; (1<<j) <=n; j++)
        for(int i=1; i+(1<<j)-1<=n; i++)
        {
            int dis=i+(1<<(j-1));
            come[i][j].val=min( come [i] [j-1].val , come [dis] [j-1] .val);
            if(come[i][j-1].val < come[dis][j-1].val) come[i][j].loc=come[i][j-1].loc;
            else come[i][j].loc = come[dis][j-1].loc;
        }

}
f solve(int l,int r)
{
    int le=len[r-l+1];
    if(come[l][le].val < come[r-(1<<le)+1] [le].val ) return come[l][le];
    else return come[r-(1<<le)+1][le];
}
void Find(int left,int right)
{
    if(left>right) return;
    f now=solve(left,right);

    ans=max(ans, now.val*(right-left+1));

    Find(left,now.loc-1);
    Find(now.loc+1,right);
}
int main()
{
    int l=0;
    for(int i=1; i<=N; i++)
    {
        if((1<<(l+1))<=i) l++;
        len[i]=l;
    }
    scanf("%d",&T);
    for(int loc=1; loc<=T; loc++)
    {
        memset(come,INF,sizeof(come));
        ans=-INF;
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        init();
        Find(1,n);
        printf("Case %d: %d\n",loc,ans);
    }
    return 0;
}

NO.4 : 笛卡爾樹的想法!

核心

這一棵樹十分的…
想象這樣一棵樹,每個子樹的根都是整顆子樹的極值,而且整棵子樹所有的點的中序遍歷順序剛好對應到了原序列中的一段連續區間。整棵樹同時滿足了堆的性質和中序遍歷即爲原數組的特點。假如有了這樣一棵樹,我們只需要dfs一遍就可以算出答案了。

input: 9 3 7 1 8 12 10 20 15 18 5

直接上圖:
這裏寫圖片描述

於是就是需要維護一個 棧 來實現 ,整一個棧都是升序排列 ,
將子樹的左邊表示 在左邊 而 右子樹表示 是 root 右邊的東西 ,這樣 就好了!

代碼:

#include <bits/stdc++.h>
using namespace std;
const int N=30005;
const int INF=0x3f3f3f3f;
stack<int> my_sta;
int T,n,root,ans,a[N],fa[N],size[N];
struct node
{
    int left,right;
} t[N];
void Build_tree()
{
    for(int i=1;i<=n;i++) t[i].left=t[i].right=-1;
    my_sta.push(1);
    for(int i=2; i<=n; i++)
    {
        int father=-1,last=-1;
        while(!my_sta.empty()&&a[my_sta.top()]>a[i])
        {
            last=my_sta.top();
            my_sta.pop();
        }
        if(!my_sta.empty())t[my_sta.top()].right=i;
        if(last!=-1)t[i].left=last;
        my_sta.push(i);
    }
}
void DFS(int root)
{
    if(t[root].left!=-1) DFS(t[root].left),size[root]+=size[t[root].left];
    if(t[root].right!=-1) DFS(t[root].right),size[root]+=size[t[root].right];
    ans=max(ans,a[root]*size[root]);
}
int main()
{
    scanf("%d",&T);
    for(int loc=1; loc<=T; loc++)
    {
        scanf("%d",&n);
        ans=-INF;
        for(int i=1;i<=n;i++) size[i]=1;
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]),fa[i]=i;
        Build_tree();
        while(!my_sta.empty()) root=my_sta.top(),my_sta.pop();
        DFS(root);
        printf("Case %d: %d\n",loc,ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章