【2019南昌邀请赛网络赛I:】Max Answer(st表+单调栈(新模版)+分析)

题目地址:https://nanti.jisuanke.com/t/38228

题意:


给出数字序列,定义一个区间内的value值是这个区间所有数之和*这个区间的最小数,求对于这个数字序列,最大的value值

 

解题思路:


本题为https://blog.csdn.net/Cassie_zkq/article/details/89792443的进阶题。

 

对正数和负数分开处理。

对于正数a[i],利用单调栈寻找以a[i]为最小值的区间,利用前缀和数组求区间和再*a[i] 即结果

但是对于负数,只有它的区间和最小对应的这个区间的value值才最小,又由区间和=sum[j]-sum[i]可知,sum[j]最小,sum[i]最大时才能时这个区间和最小,所以问题的关键转化为寻找最小的sum[j]和最大的sum[i]

对于负数a[k](数组从下标为1开始存),  i的位置位于【0,k】,j的位置位于【k,n】即使a[k]不是这个区间的最小值,之后也会有真正的最小值与sum[i]和sum[j]匹配来更新最大的value值,求区间的最值可以用st表,O(nlogn)的预处理时间,O(1)的查询时间

注意:如果数字序列为-1 -2 -3 -4 -5 那么sum[i]是可以为0的,所以i的位置位于【0,k】而不是【1,k】,否则sum[i]就会变成-1,会WA

 

找一个数左侧第一个小于等于它和右侧第一个小于它的数的单调栈模版(比两个for循环遍历代码量少,也不难理解)

    int top=0,s[maxn];s[0]=0;
    for(int i=1;i<=n;i++)
    {
        while(top>0 && a[i]<a[s[top]])//待入栈的小于栈顶元素,那么待入栈的元素就是栈顶元素右侧第一个比它(指栈顶元素)小的数
        {
            rb[s[top]]=i;//右侧第一个小于ta的
            top--;
        }
        lb[i]=s[top];//左侧第一个小于等于ta的(有重复的数字对结果并不会产生什么影响)
        //栈内从小到大有序排列,栈顶元素是要入栈元素左侧第一个小于等于它(指要入栈的元素)的数
        s[++top]=i;
    }
    while(top>0)
    {
        rb[s[top]]=n+1;
        top--;
    }

ac代码:


找正数区间去处理正数(emmm可以跳过这个代码直接看下一个,其实不用单独找正数区间的)

#include <iostream>
#include <algorithm>
#include <string>
#include <ctype.h>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <sstream>
#define maxn 600005
typedef long long ll;
using namespace std;
ll a[maxn],sum[maxn]={0},min_sum[maxn][20],max_sum[maxn][20],lb[maxn],rb[maxn];
ll n,ans=-1;
void rmq()
{
    for(int i=1;i<=n;i++)
        min_sum[i][0]=max_sum[i][0]=sum[i];
    for(int j=1;(1<<j)<=n;j++)//长度
    {
        for(int i=0;i+(1<<j)-1<=n;i++)
        {
            min_sum[i][j]=min(min_sum[i][j-1],min_sum[i+(1<<(j-1))][j-1]);
            max_sum[i][j]=max(max_sum[i][j-1],max_sum[i+(1<<(j-1))][j-1]);
        }
    }
}
void zhengshu()
{
    int l=1,r;
    while(1)
    {
        while(a[l]<=0 && l<=n) l++;//寻找正数区间的左边界,a[l]>0 或者a[n]<=0
        if(a[l]>0)
        {
            r=l;
            while(a[r+1]>0 && r<n) r++;//r为正数区间的右边界
            int top=0,s[maxn];
            s[0]=l-1;
            for(int i=l;i<=r;i++)
            {
                while(top>0 && a[i]<a[s[top]])//右侧第一个小于
                {
                    rb[s[top]]=i;
                    top--;
                }
                lb[i]=s[top];//左侧第一个小于等于
                s[++top]=i;
            }
            while(top>0)
            {
                rb[s[top]]=r+1;
                top--;
            }
            for(int i=l;i<=r;i++)
              ans=max(ans,(sum[rb[i]-1]-sum[lb[i]])*a[i]);
            l=r+1;
        }
        if(l>n) return ;
    }
}
void fushu()
{
    ll minn,maxx;
    for(int i=1;i<=n;i++)
    {
        if(a[i]<0)
        {
            int k1=(int)(log(i-0+1)/log(2.0));
            maxx=max(max_sum[0][k1],max_sum[i-(1<<k1)+1][k1]);
            int k2=(int)(log(n-i+1)/log(2.0));
            minn=min(min_sum[i][k2],min_sum[n-(1<<k2)+1][k2]);
            //cout<<maxx<<" "<<minn<<endl;
            ans=max(ans,(minn-maxx)*a[i]);
        }
    }
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    rmq();
    zhengshu();
    fushu();
    printf("%lld",ans);
    return 0;
}

不去找正数区间(找一个数右侧第一个比他小的和左侧第一个小于等与它的合并代码!!)

推荐这种方法,代码简单

#include <iostream>
#include <algorithm>
#include <string>
#include <ctype.h>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <sstream>
#define maxn 600005
typedef long long ll;
using namespace std;
ll a[maxn],sum[maxn]={0},min_sum[maxn][20],max_sum[maxn][20],lb[maxn],rb[maxn];
ll n,ans=-1;
void rmq()
{
    for(int i=1;i<=n;i++)
        min_sum[i][0]=max_sum[i][0]=sum[i];
    for(int j=1;(1<<j)<=n;j++)//长度
    {
        for(int i=0;i+(1<<j)-1<=n;i++)
        {
            min_sum[i][j]=min(min_sum[i][j-1],min_sum[i+(1<<(j-1))][j-1]);
            max_sum[i][j]=max(max_sum[i][j-1],max_sum[i+(1<<(j-1))][j-1]);
        }
    }
}
void zhengshu()
{
    int top=0,s[maxn];s[0]=0;
    for(int i=1;i<=n;i++)
    {
        while(top>0 && a[i]<a[s[top]])
        {
            rb[s[top]]=i;//右侧第一个小于ta的
            top--;
        }
        lb[i]=s[top];//左侧第一个小于等于ta的(有重复的数字对结果并不会产生什么影响)
        s[++top]=i;
    }
    while(top>0)
    {
        rb[s[top]]=n+1;
        top--;
    }
    for(int i=1;i<=n;i++)
        if(a[i]>0)
            ans = max(ans, (sum[rb[i] - 1] - sum[lb[i]]) * a[i]);
}
void fushu()
{
    ll minn,maxx;
    for(int i=1;i<=n;i++)
    {
        if(a[i]<0)
        {
            int k1=(int)(log(i-0+1)/log(2.0));
            maxx=max(max_sum[0][k1],max_sum[i-(1<<k1)+1][k1]);
            int k2=(int)(log(n-i+1)/log(2.0));
            minn=min(min_sum[i][k2],min_sum[n-(1<<k2)+1][k2]);
            ans=max(ans,(minn-maxx)*a[i]);
        }
    }
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    rmq();
    zhengshu();
    fushu();
    printf("%lld",ans);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值