单调栈

这是某次olinr巨佬给我们出的考试题。首先暴力\(O(n^2)\)是不能过的(废话),我们考虑每一个数值对答案的贡献,也就是ta能当最小值的序列个数。

倘若ta能成为最小值,那么也就是在这个数列里没有比ta大的数值,这也就转化为了求出右边第一个比ta小的值的位置和左边第一个比ta小的值的位置的问题。这完全珂以用单调栈来完成,每次栈顶的元素被弹出的值的位置就是我们所求的被弹的元素的右边第一个比ta小的值的位置。当前元素不在弹出栈顶元素的时候,我们就求出了当前的元素的左边第一个比ta小的值的位置。因为每一个数值仅仅进了一次栈,所以时间复杂度是\(O(n)\).(不要脸的搬了自己的另一篇博客里的话).

当然,你可以会只处理每一个元素的r[i],在随机数据下影响不大,但是在精心的数据下每次的转移接近为1,时间复杂度飙升到了\(O(n^2)\),会有两个点超时,所以最好同时处理l[i]和r[i].

下面是超时2个数据点的代码

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
int n,top,minn;
const int N=1000010;
int a[N],zhan[N],r[N];
long long ans;
signed main()
{
    cin>>n;
    for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    for(int i=1;i<=n;++i)r[i]=n+1;
    for(int i=1;i<=n;++i)
    {
        while(top&&a[zhan[top]]>a[i])r[zhan[top--]]=i;
        zhan[++top]=i;
    }
    for(int i=1;i<=n;++i)
    {
        minn=i;
        while(minn<=n)
        {
            ans+=(r[minn]-minn)*a[minn];
            minn=r[minn];
        }
    }
    cout<<ans;
    return 0;
}
/*
8
5 4 8 1 2 6 7 3
*/

下面是满分代码

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
int n,top,minn;
const int N=1000010;
int a[N],zhan[N],r[N],l[N];
long long ans;
signed main()
{
    cin>>n;
    for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    for(int i=1;i<=n;++i)r[i]=n+1,l[i]=0;
    for(int i=1;i<=n;++i)
    {
        while(top&&a[zhan[top]]>a[i])r[zhan[top--]]=i;
        l[i]=zhan[top];
        zhan[++top]=i;
    }
    for(int i=1;i<=n;++i)ans+=(r[i]-i)*(i-l[i])*a[i];
    cout<<ans;
    return 0;
}
/*
8
5 4 8 1 2 6 7 3
*/

有兴趣的童鞋珂以写一下类似的题目