题意:Levian在一家大公司当会计员。Levian知道公司连续n个月的营收,第i个月的营收是ai(正数表示盈利,负数为亏损)。由于疫情的隔离,第一个\(\lceil n/2 \rceil\)的收入是不稳定的,但是剩下的每个月的收入都是相同的。
Levian准备告诉董事长n - k + 1个数字---每连续k个月的总营收。换句话说,对于\(i\in{[1, n - k + 1]}\),他会说出ai + ai+1 + ... + ai+k-1个月的收入和。
不幸的是,如果一个总收入的和<= 0,那么董事长会不高兴,并且开除Levian。为了拯救Levian的职业生涯,找到一个k值,对于每k个月来说,报告的收入都要>= 0。
分析:
我们先代数分析一下,我们另Sk(i) = ai + ai+1 + ... + ai+k-1,表示从i这个起点开始,连续k个月的营收总和,当k <= n / 2时,考虑下Sk(i)和Sk(i+k),我们把k乘以2,我们的S2k(i) = Sk(i) + Sk(i+k),如果我们的Sk(i) > 0,Sk(i+k)也大于0,那么S2k(i)也是大于0的,因此如果一个合法的k是存在的,那么也存在一个k' > n / 2也是合法的。仔细想下为什么这样?因为我们要求对于i属于[1, n - k + 1]的每个起点开始长度为k的区间,它们的和都要大于> 0,那么Sk(i)和Sk(i+k)也要> 0,这样我们就会得到S2k(i) > 0,意味着,我们只需要考虑k > n / 2的,如果这个k是合法的,那么会存在一个k <= n / 2的也是合法的。
然后,当k > n / 2时,我们的[i, i + 1, i + 2, ..., i + k - 1]的最右边的数字可以肯定是x,如果x >= 0,我们考虑一下,如果存在一个k是合法的,那么k + 1也肯定是合法的,即Sk+1(i) = Sk(i) + x >= Sk(i) > 0,这样,我们只需要检查一下k == n的情况,因为既然存在一个比较小的k是合法的,当x >= 0的时候,那么后续大于k的k'也一定是合法的。
然后,我们考虑x < 0的情况,对于一个i,我们如果找到Sp(i) > 0,如果Sp(i) > 0,那么Sp-1(i) = Sp(i) + (-x) > Sp(i) > 0也是合法的,即随着k的增加,我们的区间和的值越来越低,即对于一个p来说,那么p - 1也是合法的,我们找到对于一个i来说,它所能延长的最长的k的值,即ai + ai+1 ... ai+p,之后再加一个ai+p+1就变成< 0了,这个最长的k,这个可以采用二分查找,我们先化一下公式,我们化成PreSum[i + p - 1] - PreSum[i - 1] > 0,那么对于一个i来说,我们在前缀和区间中二分查找最后一个大于PreSum[i - 1]的前缀和。
这样,我们找齐了所有i点所能延长的最大的k值(假设为limit[i]),对于一个可行的结果k值,我们要满足k <= min(limit[1], limit[2], limit[3], ..., limit[n - k]),为啥是到n - k呢?因为我们的i点只到[1, n - k],然后我们编写代码的时候,我们的i从1到n遍历的时候,因为对于一个可行的k值来说,如果i + k >= n的话,我们可以直接输出k就行了,在每一次遍历的时候,我们还要对这个k值取最小,即前面的limit。(见代码)
一个小小的疑惑:我们找每个i点所能延长的最长的k值的时候,我们用反向迭代器查找最后一个大于PreSum[i - 1]的值,我们要求的i + k值应该在[i, i + n / 2]区间内的,为什么是反着的?
有大神解释,感激不尽!
使用反向迭代器的原因:
关于查找最后一个大于PreSum[i - 1]的值,因为STL的二分查找只会查找上升的序列,我们可以反转序列或者使用反向迭代器然后进行查找。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
using LL = long long;
const int N = 1000005;
//前缀和
LL sum[N];
LL limit[N];
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= (n + 1) / 2; ++i)
{
scanf("%lld", &sum[i]);
sum[i] += sum[i - 1];
}
int x;
scanf("%d", &x);
for (int i = (n + 1) / 2 + 1; i <= n; ++i)
{
sum[i] = sum[i - 1] + x;
}
if (sum[n] > 0)
{
printf("%d\n", n);
return 0;
}
if (x > 0)
{
puts("-1");
return 0;
}
vector<LL> pre;
for (int i = 1; i <= n; ++i)
pre.push_back(sum[i]);
auto revpre = pre;
reverse(revpre.begin(), revpre.end());
for (int i = 0; i < n; ++i)
{
limit[i] = n - (upper_bound(revpre.begin(), revpre.begin() + (n + 1) / 2 - 1, (i == 0 ? 0 : pre[i - 1])) - revpre.begin());
}
LL mnlimit = 1e18;
for (int i = 1; i <= n; ++i)
{
mnlimit = min(mnlimit, limit[i]);
if (n - i + 1 <= mnlimit)
{
printf("%lld\n", mnlimit);
return 0;
}
}
puts("-1");
return 0;
}