1 条题解
-
0
题解分析
关键观察
-
从全零数组开始的最优策略
如果数组 一开始就是全零,并且以后也会被重置成全零,那么考虑后续的若干天。- 操作 1(前缀加一)会使数组保持非递增(因为对前缀加一,后面的元素不会超过前面的)。
- 而条件 要求数组是严格递增的()。
- 因此,在一次“统计并重置”操作中,最多只能有 个位置满足 (因为非递增序列中不可能有两个不同位置同时等于它们的位置编号)。
- 另外,重置后数组变为全零,所以不可能连续两天都做统计操作(第二天统计时全零,得分为 )。
综上,从全零开始,最优策略是交替执行操作 1 和操作 2: - 第 天做操作 1(使 ,其余仍为 )
- 第 天做操作 2(此时只有 ,得 分,然后重置为全零)
- 第 天再做操作 1,第 天再统计……
这样在 天中最多可以获得 分。
并且这个上界是可以达到的。
-
第一次重置的时间
由于数组初始值不一定全零,我们需要决定第一次执行统计操作发生在哪一天(记为第 天)。- 第 天之前的所有天只能执行操作 1(否则会提前重置)。
- 第 天执行统计操作,得分为当前数组中满足 的个数,记为 。
- 重置后数组变为全零,剩下的 天从全零开始,最优得分为 。
因此,如果固定第一次重置在第 天,总得分为:
我们需要对所有可能的 计算此值,并取最大值。
-
枚举的范围
可以高达 ,不能枚举所有 天。但是可以证明,只需要考虑 即可。
原因:如果第一次重置在第 天,那么经过 次前缀加操作后,每个 至少增加了 (因为每次 ,至少给 加 ,但其他位置可能加得少)。但更严格的分析是:当 时,所有 ,此时 。
于是 。
而如果我们在第一天就重置(),得分为 ,其中 是初始数组中原有的满足 的个数。显然 ,且 $\lfloor (d-1)/2 \rfloor \ge \lfloor (d-i)/2 \rfloor$(因为 )。所以 不会比任何 差。
因此只需枚举 。 -
模拟前 次操作 1
对于每个枚举的 ,我们需要知道经过前 天(每天做操作 1)后数组的状态。
由于 且 ,我们可以直接模拟:- 维护一个当前数组
cur,初始为给定的 。 - 对于第 到 天,执行前缀加操作,其中 。
- 第 天的得分为 。
每次模拟的复杂度为 ,所有 的总复杂度为 级别,因为 总和不超过 ,完全可行。
- 维护一个当前数组
-
最终算法
- 读入 ,数组 ,序列 。
- 令
limit = min(d, 2 * n)。 - 初始化
cur = a(使用 0‑索引,但条件变为cur[j] == j+1)。 - 初始化答案
ans = 0。 - 对于 到
limit:- 计算当前
cur中满足cur[j] == j+1的个数cnt。 - 更新
ans = max(ans, cnt + (d - i) // 2)。 - 如果 (且为了下一次迭代),执行第 天的操作 1:
- 取出
b = v[(i-1) % k]。 - 对
cur[0]到cur[b-1]各加 。
- 取出
- 计算当前
- 输出
ans。
注意:如果 天中一次重置都不做,得分为 ,但我们的枚举中 已经包含了第一次重置在第 天的情况, 枚举到
limit也可能包含永远不重置吗?不包含。但 时,剩余天数为 ,得分为 ,这已经考虑了最后一天重置。而一次都不重置的情况得分为 ,显然不会大于某个正得分,因此不影响答案。
复杂度
- 每个测试用例中,枚举 次,每次模拟操作 1 的复杂度为 (因为前缀加是线性的),所以总复杂度为 。
- 由于所有测试用例的 之和不超过 ,总体复杂度约为 ,非常快。
示例验证
以第一个测试用例为例:
- ,,。
limit = min(4, 6)=4。- :
cur=[1,2,3],满足 的位置有 个(全满足),得分 。然后应用第 天操作:,cur变为 。 - :
cur=[2,2,3],满足条件的位置: 成立,共 个,得分 。应用第 天操作:,cur变为 。 - :
cur=[3,3,4],无满足条件,得分 。应用第 天操作:,cur变为 。 - :
cur=[4,4,4],无满足条件,得分 。 最大值为 ,输出正确。
代码实现(C++)
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n, k, d; cin >> n >> k >> d; vector<int> a(n); for (int i = 0; i < n; ++i) cin >> a[i]; vector<int> v(k); for (int i = 0; i < k; ++i) cin >> v[i]; int limit = min(d, 2 * n); vector<int> cur = a; int ans = 0; for (int i = 1; i <= limit; ++i) { // 统计当前数组中满足 a_j == j 的个数 (1-indexed) int cnt = 0; for (int j = 0; j < n; ++j) { if (cur[j] == j + 1) ++cnt; } ans = max(ans, cnt + (d - i) / 2); // 应用第 i 天的操作1,为下一次迭代准备 if (i < d) { // 如果 i == d 则没有后续,但为了代码简洁仍可执行,不影响 int b = v[(i - 1) % k]; for (int j = 0; j < b; ++j) { cur[j]++; } } } cout << ans << '\n'; } return 0; } -
- 1
信息
- ID
- 7161
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者