2019牛客暑期多校训练营(第四场)----C-sequence

首先发出题目链接:
链接: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[l1]<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]=lr[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[l1...i1])反之,让对应 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[l1...i1])

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 l1 如果等于0怎么办如果 l − 1 l-1 l1 i − 1 i-1 i1 同时等于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;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值