「日常训练&知识学习」单调栈

这几天的知识学习比较多,因为时间不够了。加油吧,为了梦想。
这里写几条简单的单调栈作为题解记录,因为单调栈的用法很简单,可是想到并转化成用这个需要一些题目的积淀。
相关博客参见: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
%d %d
", 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
", ans);
    }
    return 0;
}

HDU 5033 Building

题意与分析

题意:有一个人在到处是高楼大厦的地方抬头仰望,假设所有高楼都在一条数轴上,给出了高楼的位置和高度,然后给出了人的位置(高度为0.....),问人能看到的阳光的最大角度是多少。
这题看起来和前面不太一样了,角度而不是最小值区间,可是想不到吧,这也可以单调栈2333
怎么写呢?容易想到,决定一个人的视角的是他左右边的建筑物之间的斜率。如果不容易想到的话,可以参见 https://www.cnblogs.com/chen9510/p/7246889.html 的文章,里面有几幅图片比较生动的表现了这个结论。
然后就是好玩的地方了:我们要在读入所有的查询后,将每个查询点视为高度为0的楼,然后再通过比较两栋相邻楼顶连线的斜率大小维护一个单调栈。这样写就和上面的套路一样一样的了,哈哈。你会从代码中发现这一点。

代码

自闭了,自己的代码死活找不到问题- -拿别人的代码出来学习吧

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn=1e5+5;
const double PI=acos(-1);
struct Node{
    double x;
    double y;
    int L,R,id;
}a[2*maxn];
int top,s[maxn];
bool cmp(const Node s1,const Node s2)
{
    return s1.x<s2.x;
}
bool cmp2(const Node s1,const Node s2)
{
    return s1.id<s2.id;
}
double cal2(double x,double y)
{
    //cout<<"H:"<<x<<" "<<y<<endl;
    return atan(y/x);
}
int main()
{
    ///cout << "Hello world!" << endl;
    int T,Case=1;  cin>>T;
    while(T--)
    {
        printf("Case #%d:
",Case++);
        int N;  scanf("%d",&N);
        for(int i=1;i<=N;i++)  scanf("%lf%lf",&a[i].x,&a[i].y),a[i].id=i;
        int Q;  scanf("%d",&Q);
        for(int i=N+1;i<=N+Q;i++)  scanf("%lf",&a[i].x),a[i].y=0,a[i].id=i;
        sort(a+1,a+N+Q+1,cmp);

        ///for(int i=1;i<=N+Q;i++) cout<<a[i].id<<" "<<a[i].x<<" "<<a[i].y<<endl;
        top=0;  s[0]=1;
        for(int i=2;i<=N+Q;i++)
        {
            while(1)
            {
                if(top<1) break;
                double x1=a[s[top]].x-a[s[top-1]].x;
                double y1=a[s[top]].y-a[s[top-1]].y;
                //double f1=cal(a[s[top-1]].x, a[s[top-1]].y, a[s[top]].x, a[s[top]].y);
                double x2=a[i].x-a[s[top]].x;
                double y2=a[i].y-a[s[top]].y;
                //double f2=cal(a[s[top]].x,   a[s[top]].y,   a[i].x, a[i].y);
                if(y1*x2>y2*x1) break;
                top--;
            }
            if(a[i].id>N) a[i].L=a[s[top]].id;//,cout<<"top1  "<<top<<" "<<s[top]<<" "<<a[s[top]].id<<endl;
            s[++top]=i;
        }

        top=0;  s[0]=N+Q;
        for(int i=N+Q-1;i>=1;i--)
        {
            while(1)
            {
                if(top<1) break;
                double x1=a[s[top-1]].x-a[s[top]].x;
                double y1=a[s[top-1]].y-a[s[top]].y;
                //double f1=cal(a[s[top-1]].x, a[s[top-1]].y, a[s[top]].x, a[s[top]].y);
                double x2=a[s[top]].x-a[i].x;
                double y2=a[s[top]].y-a[i].y;
                //double f2=cal(a[s[top]].x,   a[s[top]].y,   a[i].x, a[i].y);
                if(y1*x2<y2*x1) break;
                top--;
            }
            if(a[i].id>N) a[i].R=a[s[top]].id;//,cout<<"top2  "<<top<<" "<<s[top]<<" "<<a[s[top]].id<<endl;
            s[++top]=i;
        }

        sort(a+1,a+N+Q+1,cmp2);
        for(int i=N+1;i<=N+Q;i++)
        {
            //cout<<"--->"<<a[i].L<<" "<<a[i].R<<endl;
            double ans=PI;
            int k=a[i].L; ans-=cal2(a[i].x-a[k].x,a[k].y);
                k=a[i].R; ans-=cal2(a[k].x-a[i].x,a[k].y);
            printf("%.10f
",ans*180/PI);
        }
    }
    return 0;
}

总结

只要你能转化成离线的区间最值问题,那么单调栈有可能能够维护它(当然,问题需要满足一定的性质——这就是我们需要干的活了:直觉、转化、or玄学)。

原文地址:https://www.cnblogs.com/samhx/p/Monotonous_stack.html