首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/884/C
来源:牛客网
涉及:单调栈,线段树
点击这里回到2019牛客暑期多校训练营解题—目录贴
题目如下:
题目意思很简单,可以枚举序列
a
a
a 内的每一个值,找到最长的子序列的左右边界,使得子序列最小值为
a
a
a
假设枚举到 a [ i ] a[i] a[i],以 a [ i ] a[i] a[i] 为最小值的最长序列,左边界为 l l l,右边界为 r r r(说明 a [ l − 1 ] < a [ i ] a[l-1] < a[i] a[l−1]<a[i] 且 a [ r + 1 ] < a [ i ] a[r+1] < a[i] a[r+1]<a[i],或者 l = 1 l = 1 l=1 或者 r = n r = n r=n )
那么左边界在 [ l , i ] [l,i] [l,i] 且右边界在 [ i , r ] [i,r] [i,r] 的 a a a 的子序列都满足最小值为 a [ i ] a[i] a[i] 的要求。现在要挑选出来一个子序列使得答案最大。求以 a a a 序列每一个元素为最小值的序列的左右边界可以用单调栈来求得。
int l[maxn], r[maxn];//每一个a[i]左右两边第一个比a[i]小的数的下标
a[n+1] = a[0] = inf;//这样特殊的初始化是为了保证最后让单调栈内的元素全部出栈
for(int i = 1; i <= n+1; i++){//用vec来模拟栈,注意vec里面存的是下标
while(vec.size() && a[i] < a[vec.back()] ){
r[vec.back()] = i-1;//这里求右边界,a[i]已经比a[vec.back()]小了,所以右边界为i-1
vec.pop_back();
}
vec.push_back(i);
}
for(int i = n; i >= 0; i--){
while(vec.size() && a[i] < a[vec.back()] ){
l[vec.back()] = i+1;//这里求左边界,a[i]已经比a[vec.back()]小了,所以右边界为i+1
vec.pop_back();
}
vec.push_back(i);
}
知道的左右边界,要挑选答案最大的子序列,还要关于 b b b 序列求和,如果最小值 a [ i ] a[i] a[i] 小于0,那么尽可能让 b b b 序列对应和越小越好,反之越大越好。
求和的话先求一遍前缀和
s
u
m
sum
sum。
假设
a
[
i
]
>
0
a[i] > 0
a[i]>0 且
l
[
i
]
=
l
,
r
[
i
]
=
r
l[i] = l,r[i] = r
l[i]=l,r[i]=r,那么尽可能让对应
b
b
b 序列求和越大越好,即为
m
a
x
(
s
u
m
[
i
.
.
.
r
]
)
−
m
i
n
(
s
u
m
[
l
−
1...
i
−
1
]
)
max(sum[i...r]) - min(sum[l-1...i-1])
max(sum[i...r])−min(sum[l−1...i−1])反之,让对应
b
b
b 序列求和越小越好
m
i
n
(
s
u
m
[
i
.
.
.
r
]
)
−
m
a
x
(
s
u
m
[
l
−
1...
i
−
1
]
)
min(sum[i...r]) - max(sum[l-1...i-1])
min(sum[i...r])−max(sum[l−1...i−1])
for(int i = 1; i <= n; i++){
if(a[i] > 0)
ans = max(ans, (query_max(1, i, r[i]) - query_min(1, l[i]-1, i-1)) * a[i]);
else
ans = max(ans, (query_min(1, i, r[i]) - query_max(1, l[i]-1, i-1)) * a[i]);
}
求区间最大小值,可以用线段树(这里不要用st表,n的范围有 1 0 6 10^6 106,会爆空间)
当然有一些小细节要处理,比如 l − 1 l-1 l−1 如果等于0怎么办,如果 l − 1 l-1 l−1 与 i − 1 i-1 i−1 同时等于0怎么办,可以在 q u e r y query query 函数里面这样处理
ll query_max(int k, int l, int r){
if(l == 0){
if(r == 0) return 0; //l和r同时等于0,很明显区间最大值就是0
else return max(0ll, query_max(1, l+1, r)); //如果只是l等于0,那么可以拆成0和 query_max(1, l+1, r)求最大值
}
if(tree[k].l >= l && tree[k].r <= r){
return tree[k].maxnum;
}
int mid = (tree[k].l + tree[k].r) >> 1;
return max((l <= mid)? query_max(2*k, l, r): inf, (r > mid)? query_max(2*k+1, l, r): inf);
}
ll query_min(int k, int l, int r){
if(l == 0){
if(r == 0) return 0; //l和r同时等于0,很明显区间最小值就是0
else return min(0ll, query_min(1, l+1, r)); //如果只是l等于0,那么可以拆成0和 query_max(1, l+1, r)求最小值
}
if(tree[k].l >= l && tree[k].r <= r){
return tree[k].minnum;
}
int mid = (tree[k].l + tree[k].r) >> 1;
return min((l <= mid)? query_min(2*k, l, r): -inf, (r > mid)? query_min(2*k+1, l, r): -inf);
}
代码如下:
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 3e6+5;
const ll inf = -4e18;//表示负无穷
vector<int> vec;//单调栈
ll a[maxn], b[maxn];//题目所给变量
int l[maxn], r[maxn]; //每一个a[i]左右两边第一个比a[i]小的数的下标
struct Node{//线段树节点
ll maxnum;
ll minnum;
int l;
int r;
};
Node tree[maxn << 2];
ll ans = inf;//将答案初始化为负无穷
int n, cnt = 1;//n为题目所给变量,cnt用来建线段树
void build(int k, int l, int r){//建树
tree[k].l = l;
tree[k].r = r;
if(l == r){
tree[k].maxnum = tree[k].minnum = b[cnt++];
return;
}
int mid = (l + r) >> 1;
build(2*k, l, mid);
build(2*k+1, mid+1, r);
tree[k].maxnum = max(tree[2*k].maxnum, tree[2*k+1].maxnum);
tree[k].minnum = min(tree[2*k].minnum, tree[2*k+1].minnum);
return;
}
ll query_max(int k, int l, int r){//区间求最大值
if(l == 0){
if(r == 0) return 0; //l和r同时等于0,很明显区间最大值就是0
else return max(0ll, query_max(1, l+1, r)); //如果只是l等于0,那么可以拆成0和 query_max(1, l+1, r)求最大值
}
if(tree[k].l >= l && tree[k].r <= r){
return tree[k].maxnum;
}
int mid = (tree[k].l + tree[k].r) >> 1;
return max((l <= mid)? query_max(2*k, l, r): inf, (r > mid)? query_max(2*k+1, l, r): inf);
}
ll query_min(int k, int l, int r){//区间求最小值
if(l == 0){
if(r == 0) return 0; //l和r同时等于0,很明显区间最小值就是0
else return min(0ll, query_min(1, l+1, r)); //如果只是l等于0,那么可以拆成0和 query_max(1, l+1, r)求最小值
}
if(tree[k].l >= l && tree[k].r <= r){
return tree[k].minnum;
}
int mid = (tree[k].l + tree[k].r) >> 1;
return min((l <= mid)? query_min(2*k, l, r): -inf, (r > mid)? query_min(2*k+1, l, r): -inf);
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
a[n+1] = a[0] = -maxn;//这样特殊的初始化是为了保证最后让单调栈内的元素全部出栈
for(int i = 1; i <= n; i++) scanf("%lld", &b[i]);
for(int i = 1; i <= n; i++) b[i] += b[i-1];//求前缀和
build(1, 1, n);
for(int i = 1; i <= n+1; i++){//单调栈求l数组
while(vec.size() && a[i] < a[vec.back()] ){
r[vec.back()] = i-1;
vec.pop_back();
}
vec.push_back(i);
}
for(int i = n; i >= 0; i--){//单调栈求r数组
while(vec.size() && a[i] < a[vec.back()] ){
l[vec.back()] = i+1;
vec.pop_back();
}
vec.push_back(i);
}
for(int i = 1; i <= n; i++){
if(a[i] > 0)
ans = max(ans, (query_max(1, i, r[i]) - query_min(1, l[i]-1, i-1)) * a[i]);
else
ans = max(ans, (query_min(1, i, r[i]) - query_max(1, l[i]-1, i-1)) * a[i]);
}
printf("%lld", ans);
return 0;
}