题目
题意:给定一个数组,如果它满足
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,...,aj−1,aj)>=ai+ai+1+...+aj−1+aj,1<=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,...,aj−1,aj)>=ai+ai+1+...+aj−1+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,...,aj−1,aj)<ai+ai+1+...+aj−1+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,...,aj−1,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+...+ak−1+ak+ak−1...+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+...+ak−1)+(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+...+ak−1)+(ak+1+...+aj)>0,那么
(
a
i
+
.
.
.
+
a
k
−
1
)
(a_i+...+a_{k-1})
(ai+...+ak−1)和
(
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,...,aj−1,aj)<ai+ai+1+...+aj−1+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,...,aj−1,aj),max(ai,ai+1,...,aj−1,aj)<ai+ai+1+...+aj−1+aj,那么必定存在下标
i
,
k
−
1
,
a
i
+
.
.
.
+
a
k
−
1
>
0
i,k-1,a_i+...+a_{k-1}>0
i,k−1,ai+...+ak−1>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,...,ak−1)<ai+ai+1+...+ak−1;或者存在下标
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,...,aj−1,aj)<ak+1+...+aj−1+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
*/