题目描述
Your are given two sequences a 1 … n a_{1 \dots n} a1…n and b 1 … n b_{1 \dots n} b1…n .You need to answer max 1 ≤ l ≤ r ≤ n \displaystyle \max_{1 \le l \le r \le n} 1≤l≤r≤nmax { m i n ( a l … r ) \{min(a_{l \dots r}) {min(al…r) × s u m ( b l … r ) } \times sum(b_{l \dots r})\} ×sum(bl…r)}, 1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1≤l≤r≤n,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} a1…n.The third line contains n integers
meaning b 1 … n b_{1 \dots n} b1…n.
输出描述:
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 1≤n≤3×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 −106≤ai,bi≤106,1≤n≤3×106 , − 1 0 6 ≤ a i , b i ≤ 1 0 6 −10^6 ≤a i ,b i ≤10^6 −106≤ai,bi≤106.
题目的意思很明确:给出两个长度相同的序列,序列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为负数相似。
最后,为了避免翻车每个点当成一个区间去求一下,当然代码在前面做一下比较好。