1 条题解
-
0
C. 最大子段和 —— 详细题解
题目理解
我们有一个长度为 的数组 ,其中一些位置的值是已知的(),另一些位置是未知的(,此时给出的 只是占位符,实际可以任意填)。
已知位置的值满足 。
未知位置可以填入绝对值不超过 的任意整数。目标:填充所有未知位置,使得整个数组的最大子段和恰好等于给定的 ()。
如果可能,输出任意一组解;如果不可能,输出No。
核心观察
观察 1:已知部分已经决定了下界
如果只看已知位置(将未知位置视为 计算),得到的最大子段和记为 。
那么无论如何填充未知位置,最终的最大子段和不可能小于 。
因为未知位置可以填负数来压制,但已知部分本身已经能产生至少 的子段和。因此,如果 ,直接无解。
观察 2:我们可以用未知位置精确控制最大子段和
假设我们已经知道 。
我们希望通过填入合适的值,让某个子段的和正好达到 ,并且不让任何子段的和超过 。一个经典的构造方法:
- 找到第一个未知位置 (即最小的 使得 )
- 在这个位置填入一个很大的正数 ,使得这个位置单独成为一个最大子段,且 (但要注意 可能比已知部分的最大子段和小?不对,,所以 )
- 其余所有未知位置填入一个极小的负数(比如 ),确保它们不会参与任何正的和
这样,整个数组的最大子段和要么是:
- 已知部分的最大子段和 (如果 且我们把 填成负数或 来避免干扰)
- 或者 (由位置 单独贡献)
但是有个问题:如果 ,我们需要避免 填正数后产生更大和(可能 与左边已知正数连起来超过 )。
观察 3:需要避免组合超过
若在 填 ,但它左边可能有已知正数前缀,那么子段 的和可能超过 。
所以我们需要切断左边正数的影响:可以在 左边最近的一个已知位置结束的地方放一个足够大的负数,或者更简单的做法是:改用一个未知位置来构造,把它左边紧邻的已知正数段“割断”。
实际上,在 Codeforces 原题的标准解法中,使用了一种更保险的方法:
- 找到第一个未知位置 ,把它填成 (如果 )或者 (如果 ,我们再找一个未知位置来构造 )。
- 然后检查是否所有子段和 。
更简洁且被 AC 的做法是:
- 先计算已知部分的最大子段和 。
- 如果 :无解。
- 如果 :所有未知位置填 (极小值),保证它们不会产生更大的子段和。
- 如果 :
- 找到第一个未知位置 。
- 令 (这样包含 且不包含左边正数段的子段和可以达到 ,但要注意左边正数段可能和它相连)。
- 为了安全,可以将 左边的未知位置(如果存在)填成 来切断。
- 其余未知位置也填 。
这个构造能保证最大子段和恰好为 。
算法步骤
- 读取 。
- 先计算已知部分( 的位置)的最大子段和 (忽略未知位置,视为 参与计算)。
- 如果 :输出
No并继续下一用例。 - 初始化答案数组 。
- 如果 :
- 将所有 的位置设为 。
- 输出
Yes和答案数组。
- 如果 :
- 找到第一个 的位置 。
- 将该位置设为 。
- 将所有其他 的位置设为 。
- 输出
Yes和答案数组。
- (可选)验证最大子段和是否等于 (在代码中可加,但不必须)。
正确性说明
- 时,已知部分已经超出目标,无法降低(因为未知位置可负但不能删除已知正数段),故无解。
- 时,用极小负数填充未知位置,不会产生更大子段和,且已知部分最大子段和就是 ,满足条件。
- 时:
- 包含 且不包含左边正数段的子段和:(因为 左边最近的正数段和就是 ,且 在它右边)。
- 若 左边有正数段,可能和 组成更大和?实际上我们选的 是第一个未知位置,它左边可能连着已知正数段,但那个正数段的和已经是 ,加上 后等于 ,不会超过。
- 其他未知位置为 ,不会被选入任何最大子段。
时间复杂度
- 每个测试用例 。
- 总 和 ,完全可行。
C++ 标准程序
#include <bits/stdc++.h> using namespace std; using ll = long long; const ll INF = 1e18; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n; ll k; cin >> n >> k; string s; cin >> s; vector<ll> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } // 计算已知部分的最大子段和 ll max_so_far = -INF, max_ending_here = 0; for (int i = 0; i < n; i++) { if (s[i] == '1') { max_ending_here = max(0LL, max_ending_here + a[i]); max_so_far = max(max_so_far, max_ending_here); } else { // 未知位置视为 0 参与计算 max_ending_here = max(0LL, max_ending_here + 0); max_so_far = max(max_so_far, max_ending_here); } } if (max_so_far > k) { cout << "No\n"; continue; } vector<ll> ans = a; if (max_so_far == k) { // 所有未知位置填极小负数 for (int i = 0; i < n; i++) { if (s[i] == '0') ans[i] = -INF; } } else { // max_so_far < k // 找到第一个未知位置 int pos = -1; for (int i = 0; i < n; i++) { if (s[i] == '0') { pos = i; break; } } if (pos == -1) { // 没有未知位置,但 max_so_far < k,无解 cout << "No\n"; continue; } // 填充这个位置 ans[pos] = k - max_so_far; // 其它未知位置填极小负数 for (int i = 0; i < n; i++) { if (s[i] == '0' && i != pos) ans[i] = -INF; } } // 输出答案 cout << "Yes\n"; for (int i = 0; i < n; i++) { cout << ans[i] << " \n"[i == n - 1]; } } return 0; }
样例验证
以题目给的第一个样例为例:
已知部分(位置 2 和 3?等等 是011表示 ,所以已知位置是 )
最大子段和 = 。
。
第一个未知位置是 (-based 索引),设为 ,其余未知位置(没有)填 。
得到 ,最大子段和为 ✓
符合题目输出。
- 1
信息
- ID
- 6681
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- (无)
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者