D. Max GEQ Sum(思维/单调栈/区间查询/前缀和)

题目
题意:给定一个数组,如果它满足 m a x ( a i , a i + 1 , . . . , a j − 1 , a j ) > = a i + a i + 1 + . . . + a j − 1 + a j , 1 < = i < = j < = n max(a_i,a_{i+1},...,a_{j-1},a_j)>=a_i+a_{i+1}+...+a_{j-1}+a_j,1<=i<=j<=n max(ai,ai+1,...,aj1,aj)>=ai+ai+1+...+aj1+aj1<=i<=j<=n,那么输出YES,否则输出NO。

官方题解参考
思路:要使 m a x ( a i , a i + 1 , . . . , a j − 1 , a j ) > = a i + a i + 1 + . . . + a j − 1 + a j max(a_i,a_{i+1},...,a_{j-1},a_j)>=a_i+a_{i+1}+...+a_{j-1}+a_j max(ai,ai+1,...,aj1,aj)>=ai+ai+1+...+aj1+aj对于所有的 i < = j i<=j i<=j成立,我们只需找,是否存在反例即可。
看是否存在 m a x ( a i , a i + 1 , . . . , a j − 1 , a j ) < a i + a i + 1 + . . . + a j − 1 + a j max(a_i,a_{i+1},...,a_{j-1},a_j)<a_i+a_{i+1}+...+a_{j-1}+a_j max(ai,ai+1,...,aj1,aj)<ai+ai+1+...+aj1+aj
假设最大值为 a k = m a x ( a i , a i + 1 , . . . , a j − 1 , a j ) , i < = k < = j a_k=max(a_i,a_{i+1},...,a_{j-1},a_j),i<=k<=j ak=max(ai,ai+1,...,aj1,aj),i<=k<=j,原式拆分为 a k < a i + . . . + a k − 1 + a k + a k − 1 . . . + a j a_k<a_i+...+a_{k-1}+a_k+a_{k-1}...+a_j ak<ai+...+ak1+ak+ak1...+aj,即 0 < ( a i + . . . + a k − 1 ) + ( a k + 1 + . . . + a j ) 0<(a_i+...+a_{k-1})+(a_{k+1}+...+a_j) 0<(ai+...+ak1)+(ak+1+...+aj)。如果 ( a i + . . . + a k − 1 ) + ( a k + 1 + . . . + a j ) > 0 (a_i+...+a_{k-1})+(a_{k+1}+...+a_j)>0 (ai+...+ak1)+(ak+1+...+aj)>0,那么 ( a i + . . . + a k − 1 ) (a_i+...+a_{k-1}) (ai+...+ak1) ( a k + 1 + . . . + a j ) (a_{k+1}+...+a_j) (ak+1+...+aj)至少有一个大于0。
也就是说,如果 m a x ( a i , a i + 1 , . . . , a j − 1 , a j ) < a i + a i + 1 + . . . + a j − 1 + a j max(a_i,a_{i+1},...,a_{j-1},a_j)<a_i+a_{i+1}+...+a_{j-1}+a_j max(ai,ai+1,...,aj1,aj)<ai+ai+1+...+aj1+aj,且最大值不是边界值,即存在下标 i < k < j i<k<j i<k<j,使得 a k = m a x ( a i , a i + 1 , . . . , a j − 1 , a j ) , m a x ( a i , a i + 1 , . . . , a j − 1 , a j ) < a i + a i + 1 + . . . + a j − 1 + a j a_k=max(a_i,a_{i+1},...,a_{j-1},a_j),max(a_i,a_{i+1},...,a_{j-1},a_j)<a_i+a_{i+1}+...+a_{j-1}+a_j ak=max(ai,ai+1,...,aj1,aj),max(ai,ai+1,...,aj1,aj)<ai+ai+1+...+aj1+aj,那么必定存在下标 i , k − 1 , a i + . . . + a k − 1 > 0 i,k-1,a_i+...+a_{k-1}>0 i,k1,ai+...+ak1>0,即 m a x ( a i , a i + 1 , . . . , a k − 1 ) < a i + a i + 1 + . . . + a k − 1 max(a_i,a_{i+1},...,a_{k-1})<a_i+a_{i+1}+...+a_{k-1} max(ai,ai+1,...,ak1)<ai+ai+1+...+ak1;或者存在下标 k + 1 , j , a k + 1 + . . . + a j > 0 k+1,j,a_{k+1}+...+a_j>0 k+1,j,ak+1+...+aj>0,即 m a x ( a k + 1 , . . . , a j − 1 , a j ) < a k + 1 + . . . + a j − 1 + a j max(a_{k+1},...,a_{j-1},a_j)<a_{k+1}+...+a_{j-1}+a_j max(ak+1,...,aj1,aj)<ak+1+...+aj1+aj
因此,要找反例,我们只需关注每个数,左右相邻的、值不超过它的最大连续子区间即可。

  • 如何确定每个数的最大不超过它的左右边界?单调栈。
  • 如何快速查询多个子区间的最大值?区间查询+前缀和
  • 区间查询?用线段树、树状数组(适用于动态查询)或rmq(适用于静态查询)
#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3f
const int maxn = 200010;

int n;
int a[maxn];
ll pre[maxn], suf[maxn];
int Left[maxn], Right[maxn];
ll sufTree[maxn<<2], preTree[maxn<<2];

// 单调栈求每个数的最大值的 "统治领域"
void calIndex() {
	stack<int> st;
	for (int i = 1; i <= n; ++i) {
		Right[i] = n+1;
		while (!st.empty() && a[st.top()] < a[i]) {
			Right[st.top()] = i;
			st.pop();
		}
		st.push(i);
	}
	while (!st.empty()) {
		st.pop();
	}
	for (int i = n; i >= 1; --i) {
		Left[i] = 0;
		while (!st.empty() && a[st.top()] < a[i]) {
			Left[st.top()] = i;
			st.pop();
		}
		st.push(i);
	}
}
// 构建线段树 
void build(ll *tree, ll *p, int rt, int l, int r) {
	if (l == r) {
		tree[rt] = p[l];
		return;
	}
	int m = (l + r) >> 1;
	build(tree, p, rt << 1, l, m);
	build(tree, p, rt << 1 | 1, m + 1, r);
	tree[rt] = max(tree[rt<<1], tree[rt<<1|1]);
}
// 区间查询 [a,b]最大值 
ll query(ll *tree, int rt, int l, int r, int a, int b) {
	if (a > b) {
		return -inf;
	}
	if (l >= a && r <= b) {
		return tree[rt];
	}
	int m = (l + r) >> 1;
	ll ans = -inf;
	if (a <= m) ans = max(ans, query(tree, rt<<1, l, m, a, b));
	if (m < b) ans = max(ans, query(tree, rt<<1|1, m+1, r, a, b));
	return ans;
}
void solve() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &a[i]);
	}
	// 前缀和 
	pre[1] = a[1];
	for (int i = 2; i <= n; ++i) {
		pre[i] = pre[i-1] + a[i];
	} 
	// 后缀和 
	suf[n] = a[n];
	for (int i = n - 1; i >= 1; --i) {
		suf[i] = suf[i+1] + a[i];
	}
	/*for (int i = 1; i <= n; ++i) {
		printf("%d %d --\n", pre[i], suf[i]);
	}*/
	calIndex();
	
	build(preTree, pre, 1, 1, n);
	build(sufTree, suf, 1, 1, n);

	bool flag = true;
	/*for (int i = 1; i <= n; ++i) {
		printf("{%d %d}\n", pre[i], preTree[i]);
	}*/
//	printf("debug %lld\n", query(preTree, 1, 1, n, 1, 1));
	
	for (int i = 1; i <= n; ++i) {
		// 查询左边界取值 [i+1, Right[i]-1], 右边界固定 Right[i]-1的所有区间的最大值
		// 等价于查询 前缀和数组 [i+1, Right[i]-1]的最大值 - pre[i] 
		ll rmx = query(preTree, 1, 1, n, i + 1, Right[i] - 1) - pre[i];
		// 查找 右边界取值 [Left[i] + 1, i - 1}], 左边界固定 Left[i] + 1 的所有区间的最大值
		// 等价于 查询 后缀和数组 [Left[i] + 1, i - 1}]的最大值 - suf[i]
		ll lmx = query(sufTree, 1, 1, n, Left[i] + 1, i - 1) - suf[i];
		if (max(rmx, lmx) > 0LL) {
			flag = false;
			break;
		}
	}
	printf("%s\n", flag ? "YES" : "NO");
}
int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
}
/*
3
4
-1 1 -1 2
5
-1 2 -3 2 -1
3
2 3 -1


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值