洛谷P2422 良好的感觉(简单数据结构复习)

这道题不是很难,只是太久没有敲过ST表和单调栈,就当贴个板子咯。而且,这道题的做法有很多,也比较常用,所以写一写这个题的题解吧 (*^__^*) 嘻嘻~

题目链接:洛谷 P2422
题目大意:找出一段区间,使得“区间最小值×区间和”最大。
题解:

  • 单调栈
    这种题有一个常见的思路是用枚举最小值是谁,然后就能确定区间长度。所以可以用正反两遍单调栈,处理出某个点作为最小值时左右两边各能扩展到哪里。最后找最大的a[i]*(sum[r[i]]-sum[l[i]-1])。
    还有一种方法是之维护一个顶部最小的单调栈,需要弹出栈顶时即栈顶左右两侧比它小的位置都已经确定,更新一次答案即可。

  • 分治+ST表
    这种把区间分开的思路也比较好用。从最小的元素开始,此时最优的区间即整个区间,直接 min*(r-l+1),然后,最小值的位置把序列分成两部分(或者一部分,即取序列端点元素时),每一部分再找最小值,乘区间和,递归求解。f[][]是ST表记录区间最小值,g[][]记录最小值的位置。

下面贴代码 *^_^ * ~~~(有参考大牛们(⊙v⊙))

( 4种,暴力 / ST表 / 两遍单调栈 / 一遍单调栈)
code_1——暴力

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read()
{
    char c=getchar(); int num=0,f=1;
    while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }
    while (c<='9'&&c>='0') { num=num*10+c-'0'; c=getchar(); }
    return num*f;
}
int n,a[100005];
long long ans=0;
int main()
{
    n=read();
    for (int i=1;i<=n;i++) a[i]=read();
    for (int i=1;i<=n;i++)
    {
        long long sum=a[i],l=i-1,r=i+1;
        while (a[l]>=a[i]&&l>=1)
         sum+=a[l],l--;
        while (a[r]>a[i]&&r<=n)
         sum+=a[r],r++;
         ans=max(ans,sum*a[i]);
    }
    cout<<ans;
    return 0;
}

code_2——ST表

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100005
using namespace std;
inline int read()
{
    char c=getchar(); int num=0,f=1;
    while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }
    while (c<='9'&&c>='0') { num=num*10+c-'0'; c=getchar(); }
    return num*f;
}
int n,tpa,tpb,f[N][22],g[N][22];
long long sum[N],ans=0;
void find(int l,int r)
{
    int k=log(r-l+1)/log(2);
    tpa=min(f[l][k],f[r-(1<<k)+1][k]);
    tpb=(tpa==f[l][k])?g[l][k]:g[r-(1<<k)+1][k];
    ans=max(ans,(sum[r]-sum[l-1])*tpa);
    if (l==r) return;
    if (tpb!=l) find(l,tpb-1);
    if (tpb!=r) find(tpb+1,r);
}
int main()
{
    n=read();
    for (int i=1;i<=n;i++)
    {
        f[i][0]=read();
        g[i][0]=i;
        sum[i]=sum[i-1]+f[i][0];
    }
    for (int j=1;j<=19;j++)
     for (int i=1;i+(1<<j)-1<=n;i++)
     {
         f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][i-1]);
         g[i][j]=(f[i][j]==f[i][j-1])?g[i][j-1]:g[i+(1<<(j-1))][j-1];
     }
    find(1,n);
    printf("%lld",ans);
    return 0;
}

code_3——两遍单调栈

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read()
{
    char c=getchar(); int num=0,f=1;
    while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }
    while (c<='9'&&c>='0') { num=num*10+c-'0'; c=getchar(); }
    return num*f;
}
int n,a[100005],top=0,sta[100005],l[100005],r[100005];
long long sum[100005],ans=0;
int main()
{
    n=read();
    for (int i=1;i<=n;i++)
    {
        a[i]=read();
        sum[i]=sum[i-1]+a[i];
    }
    for (int i=1;i<=n;i++)
    {
        while (top&&a[sta[top]]>a[i]) r[sta[top--]]=i;
        sta[++top]=i;
    }
    top=0;
    for (int i=n;i>=1;i--)
    {
        while (top&&a[sta[top]]>a[i]) l[sta[top--]]=i;
        sta[++top]=i;
    }
    for (int i=1;i<=n;i++)
     ans=max(ans,a[i]*(sum[r[i]-1]-sum[l[i]]));
    cout<<ans;
}

code_4——一遍单调栈

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read()
{
    char c=getchar(); int num=0,f=1;
    while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }
    while (c<='9'&&c>='0') { num=num*10+c-'0'; c=getchar(); }
    return num*f;
}
int n,top=0;
int sta[100005],ne,a[100005];
long long sum[100005],ans=0;
int main()
{
    n=read();
    for (int i=1;i<=n;i++)
    {
        a[i]=read();
        sum[i]=sum[i-1]+a[i];
        while (top&&(a[sta[top]]>=a[i]))
        {
            ans=max(ans,(sum[i-1]-sum[sta[top-1]])*a[sta[top]]);
            top--;
        }
        sta[++top]=i;
    }
    while (top)
    {
        ans=max(ans,(sum[n]-sum[sta[top-1]])*a[sta[top]]);
        top--;
    }
    cout<<ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值