2019牛客多校训练营(第四场)——C

原题题址

题目描述

Your are given two sequences a 1 … n a_{1 \dots n} a1n and b 1 … n b_{1 \dots n} b1n .You need to answer max ⁡ 1 ≤ l ≤ r ≤ n \displaystyle \max_{1 \le l \le r \le n} 1lrnmax { m i n ( a l … r ) \{min(a_{l \dots r}) {min(alr) × s u m ( b l … r ) } \times sum(b_{l \dots r})\} ×sum(blr)} 1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1lrn,Where min(a) means the minimal value of every element of sequence a, sum(a) means the sum of every element of sequence a .

输入描述:

The first line contains an integer n .The second line contains n
integers meaning a 1 … n a_{1 \dots n} a1n.The third line contains n integers
meaning b 1 … n b_{1 \dots n} b1n.

输出描述:

An integer meaning the answer.

示例1

输入

3
1 -1 1
1 2 3

输出

3

备注:
For all test datas, 1 ≤ n ≤ 3 × 1 0 6 1 \leq n \leq 3 \times 10^6 1n3×106, − 1 0 6 ≤ a i , b i ≤ 1 0 6 , 1 ≤ n ≤ 3 × 1 0 6 -10^6 \leq a_i,b_i \leq 10^6,1≤n≤3×10^6 106ai,bi106,1n3×106 , − 1 0 6 ≤ a i , b i ≤ 1 0 6 −10^6 ≤a i ,b i ≤10^6 106ai,bi106.

题目的意思很明确:给出两个长度相同的序列,序列a和序列b,现在定义一个值 A ( l , r ) A(l,r) A(l,r), A ( l , r ) A(l,r) A(l,r)的定义是,在区间 [ l , r ] [l,r] [l,r]内的序列a的最小值 m i n { a l , a l + 1 . . . a r } min\{a_{l},a_{l+1}...a_{r}\} min{al,al+1...ar} 乘上区间 [ l , r ] [l,r] [l,r]内b序列的区间和 s u m { a l , a l + 1 . . . a r } sum\{a_{l},a_{l+1}...a_{r}\} sum{al,al+1...ar}
顺着题意,就是把根据区间(确定区间,就确定了 A ( l , r ) A(l,r) A(l,r)和的部分,此时需要求区间的最小值)来求a序列在 [ l , r ] [l,r] [l,r]的最小值。
上面说的方法要枚举区间,显然,枚举每个区间是不可行的。
那么我们可不可以反过来,不确定区间来找最小值的点,而是根据点管辖的区间,来找区间内和的最大值。

区间的和的值的维护可以用线段树或者树状数组,因为不需要修改值,线段树也不难写,我们就用线段树吧。建树 O ( n ) O(n) O(n)的复杂度,单次查询 O ( l o g n ) O(logn) O(logn),需要查 n n n次,总共就 O ( n l o g n ) O(nlogn) O(nlogn)吧。可行的,只要点管辖的区间确定的算法不超过 O ( n l o g n ) O(nlogn) O(nlogn)也都行,如果你确定点管辖的区间用枚举,那前面的方法白瞎了。

点管辖的区间啊,牛客多校第一场的A题很相似捏,那个题目只需要判断序列变化趋势,那么相当于变相地求了,每个点作为最小值所管辖的区间。当然了,方法还是两种了,笛卡尔树,单调栈。作为蒟蒻,当然选择简单的单调栈了,虽然笛卡尔树建成后,操作异常简单,但是不会笛卡尔树啊。

做题思路一个是这样了:
会的就可以自己试试了。
不会的先看代码
代码:
**写在前面:**由于线段树从1开始的原因,我们要把下标整体加后移一位(前缀和的最前面的0也要加进去)

#include"bits/stdc++.h"
using namespace std;
typedef long long ll;
const int maxn =  3e6+5;
const ll INF = 1e18+100;
int N,M,a[maxn],b[maxn],S[maxn],top = 0;
ll sum[maxn],ans1 = -INF,ans2 = INF;
struct qj{
    int op,ed;
}d[maxn];      
                    
struct TreeNode{
    long long Max,l,r,Min;
}Tree[maxn<<2];
void Buildtree(int index,int L,int R){
    Tree[index].l = L;
    Tree[index].r = R;
    Tree[index].Max = -INF;
    Tree[index].Min = INF;
    if(Tree[index].l==Tree[index].r){
          Tree[index].Max = sum[Tree[index].l];
          Tree[index].Min = sum[Tree[index].l];
          return;
    }
    int Mid = (L+R)>>1;
    Buildtree(index<<1,L,Mid);
    Buildtree(index<<1|1,Mid+1,R);
    Tree[index].Max = max(Tree[index<<1].Max,Tree[index<<1|1].Max);
    Tree[index].Min = min(Tree[index<<1].Min,Tree[index<<1|1].Min);
} 
void QueryMax(int index,int L,int R){
    if(Tree[index].l>=L&&Tree[index].r<=R){
        ans1 = max(ans1,Tree[index].Max);
        return;
    }
    int Mid = (Tree[index].r+Tree[index].l)>>1;
    if(Mid>=L)QueryMax(index<<1,L,R);
    if(Mid<R)QueryMax(index<<1|1,L,R);
//  if(Mid>=L)Query(index<<1,L,R);
//  else if(Mid<R)Query(index<<1|1,L,R);
//  else{
//      Query(index<<1,L,Mid);
//      Query(index<<1|1,Mid+1,R);
//  }
}
void QueryMin(int index,int L,int R){
    if(Tree[index].l>=L&&Tree[index].r<=R){
        ans2 = min(ans2,Tree[index].Min);
        return;
    }
    int Mid = (Tree[index].r+Tree[index].l)>>1;
    if(Mid>=L)QueryMin(index<<1,L,R);
    if(Mid<R)QueryMin(index<<1|1,L,R);
    }
int main(){
    scanf("%d",&N);
    N++;
    for(int i = 2;i <= N;i++)
    scanf("%d",&a[i]);
    sum[1] = 0;
    for(int i = 2;i <= N;i++)
    {
    scanf("%d",&b[i]);
    sum[i] = sum[i-1]+b[i];;
    }
    for(int i = 2;i <= N;i++){
        d[i].op = i;
        if(top==0)
        S[++top] = i;
        else{
            int tmp,flag = 0;
            while(a[S[top]]>a[i]&&top>0){
            tmp = d[S[top]].op;
            d[S[top]].ed = d[i].op - 1;
            flag = 1;
            top--;
            }
            if(flag){
                d[i].op = tmp;
            }
            S[++top] = i;  
        }
    }
    for(int i = 1;i <= top;i++)
        d[S[i]].ed = N;
    Buildtree(1,1,N);
    long long Max = -INF ;
    for(int i = 2;i <= N;i++){
        int L = d[i].op,R = d[i].ed;
        ans1 = -INF;
        ans2 = INF;
        ll DE;
        if(a[i]>0)   {
         QueryMax(1,i,R);
         QueryMin(1,L-1,i-1);
          DE = ans1 - ans2;
        }
        else {
             QueryMax(1,L-1,i-1);
             DE = ans2 - ans1;
        Max = max(Max,DE*a[i]);
    }
    cout << Max;
    return 0;
}

线段树无需多言,就是个板子,要讲的话,还要一篇长博客,在此就不赘述了。

主要就是讲讲单调栈怎么维护点的管辖区间。
区间起点:
对于不弹栈就加入的元素,它的值小于它的前一个元素,它的其实区间起点就是他自己所在的下标。
对于弹栈的才能加入的元素,它最后弹掉的点就是它管辖区间内处它以外的最小点,也就是次小点,当然次小点的地盘也要被最小点管辖,所以最小点管辖区间的起点就是它弹掉最后一个元素的起点。
区间终点:
对于被弹掉的元素,终点就是弹掉它的元素的起点-1,可以看做它的区间管辖延伸到弹掉它的元素时,它的管辖区间就无法继续延伸下去,就是弹掉它的元素的起点-1(不包括弹掉它的元素)。
对于最后没出栈的,就是最后还在栈内的元素,可以直接全部把终点定为序列终点N,也可以认为再加一个无穷小的元素来使之前的元素全部出栈,那就把这种情况变成了普通情况,不需要再写其他代码。

最后的最后要讲一下的是,求最值的时候应该在点的两边去分别找一个最大和最小,否则可能区间不包括点,要不然就会错(还能过样例)。

追加一段代码:(这个是我同学网上找的,如果原作者发现了我不小心引用了您的代码,请与我联系,这个版权问题再商量)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e6+10;
ll ans,sum,mn;
ll a[maxn],b[maxn];
int main()
{
    int n; scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    scanf("%lld",&b[1]);
    sum=b[1],mn=a[1];
    ans=b[1]*a[1];
    for(int i=2;i<=n;i++){ //最小值为正
        scanf("%lld",&b[i]);
        ans=max(ans,b[i]*a[i]);
        if(sum<0)
            sum=b[i],mn=a[i];
        else{
            mn=min(mn,a[i]); //更新区间最小值
            sum+=b[i]; //更新区间和
            ans=max(ans,sum*mn);
        }
    }
    sum=b[1],mn=a[1];
    for(int i=2;i<=n;i++){ //最小值为负
        if(sum>0)
            sum=b[i],mn=a[i];
        else{
            mn=min(mn,a[i]);
            sum+=b[i];
            ans=max(ans,sum*mn);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

那么这段代码就厉害了,代码量这么短,还容易实现。

这也是基于点去寻找区间的,不同于管辖区间,它是直接找到,最好区间,可真是太强了。

我们要知道,对于 s u m { b x . . . . b y } sum\{b_{x}....b_{y}\} sum{bx....by}(最好区间), a i a_{i} ai的正负是有影响的。那么分正负讨论,正与负对应的,我们就讨论的情况。
a i a_{i} ai为正数时,我们要 s u m { b x . . . . b y } sum\{b_{x}....b_{y}\} sum{bx....by}为正才更满足我们要求。
此时,一旦sum的值为负,我们就不要以前的区间了。因为要了前面的区间再怎样弄,sum的值都会是比不取前一个区间要小的。懂了这一点,题目就可以过了,每次更新了最小值和sum后就乘起来与ans比较大小,在更新ans。经过这个过程 a i a_{i} ai为正值情况就搞定了,此过程中虽然有负值当成正值弄了,但不会影响结果。
a i a_{i} ai为负数相似。
最后,为了避免翻车每个点当成一个区间去求一下,当然代码在前面做一下比较好。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值