1 条题解
-
0
题意简述
给定长度为 的数组 和阈值 。对于每个子段 ,从左到右累加 到计数器 ,若中途 则将 重置为 。求最终 的子段个数。
关键观察
当遇到某个位置 使得前缀和超过 时, 就会清零。此后,该位置右侧的统计相当于从该位置重新开始计数。
我们可以从右往左进行 DP。设 表示从第 个蘑菇开始,以它为左端点 ,且最终 的右端点 的个数(也就是对所有 ,子段 最后 的数量)。注意,这里的计数方式依赖于代码中的定义:代码中 实际上统计的是“以 开头且最终 的子段数”,并且利用了前缀和和二分。
推导
令前缀和数组 ,其中 ()。当我们从位置 开始累加时,如果在位置 处(即 )仍满足条件,而在 处首次超过 ,则游戏会在 处清零,后续相当于从 重新开始。
那么对于左端点 :
- 如果一直累加到末尾都不超过 ,则所有右端点 都满足最终 ,贡献为 。
- 否则,设第一个超过 的位置为 ,那么在 范围内的右端点都满足条件,因为在这些右端点处游戏还未清零;而 处会清零,之后的情况等价于以 为起点重新开始。因此有递推关系: 其中 是满足 的最小位置。
在代码实现中,将 数组倒过来计算,并利用前缀和 (实际代码中 被原地改为前缀和)和二分查找 :
q = upper_bound(a.begin(), a.end(), a[i] + x) - a.begin();(注意这里 是前缀和 ,所以 对应 ,二分查找第一个 的位置 )。于是 ,含义为:从 开始,清零发生在 (注意下标换算), 内的右端点有 个,再加上从 开始的后续方案 。最终答案就是 (代码中 从 到 )。
时间复杂度
对每个测试用例,前缀和 ,反向遍历 ,每次二分 ,总复杂度 ,满足 的限制。
参考代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { cin.tie(nullptr)->sync_with_stdio(false); int tests; cin >> tests; while (tests--) { int n; ll x; cin >> n >> x; vector<ll> a(n + 1); for (int i = 1; i <= n; ++i) cin >> a[i]; // 计算前缀和,原地覆盖 partial_sum(a.begin() + 1, a.end(), a.begin() + 1); vector<int> dp(n + 2, 0); // dp[n+1] 默认为0 for (int i = n - 1; i >= 0; --i) { int q = upper_bound(a.begin(), a.end(), a[i] + x) - a.begin(); dp[i] = dp[q] + q - i - 1; } cout << accumulate(dp.begin(), dp.begin() + n, 0ll) << '\n'; } }
- 1
信息
- ID
- 6917
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者