1 条题解
-
0
题目解析
问题重述
给定一个数组 ,其中除了最多一个元素外,其余元素都是 或 。那个特殊的元素 可以是任意整数()。需要找出所有可能的子段和(包括空子段,和为 ),按升序输出。
核心思路
1. 简化问题:全是 或
如果所有元素都是 或 ,那么对于任意固定左边界 ,随着右边界 向右移动,子段和每次变化 。因此,从该左边界出发可以得到从最小值到最大值的所有整数。由于所有子段都包含空子段(和为 ),所有可能子段和的并集也是一个连续的区间 。
所以,当数组只有 时,答案就是从最小子段和到最大子段和的所有整数。
2. 包含特殊元素的情况
数组最多有一个特殊元素 (既不是 也不是 )。将子段分为两类:
- 不包含特殊元素的子段:这些子段完全由 组成,其和构成一个连续区间 (包含 )
- 包含特殊元素的子段:去掉特殊元素后,剩余部分由 组成,其和构成一个连续区间 (包含 )。加上特殊元素后,和变为
3. 最终答案
最终所有可能的子段和是这两个区间的并集:
由于两个区间都是连续的整数区间,它们的并集可能是:
- 一个连续区间(如果有重叠或相邻)
- 两个分离的区间(如果没有重叠)
4. 如何求
利用前缀和技巧:子段 的和 = 。
固定右边界 ,最大子段和 = ,最小子段和 = 。
遍历数组时,我们维护:
- 前缀和
- 到当前位置为止的最小前缀和 和最大前缀和
- 特殊元素出现前的最小/最大前缀和(用于不包含特殊元素的子段)
- 特殊元素出现后的最小/最大前缀和(用于包含特殊元素的子段)
算法步骤
-
初始化:
- (不包含特殊元素的子段和范围)
- (包含特殊元素的子段和范围)
- (当前前缀和)
- (到当前位置的最小/最大前缀和)
- (特殊元素出现前的最小/最大前缀和)
-
遍历数组:
- 更新前缀和
- 如果遇到特殊元素():
- 保存特殊元素前的范围:
- 重置特殊元素后的范围:
- 更新不包含特殊元素的子段和范围:
- 更新包含特殊元素的子段和范围:
- 更新最小/最大前缀和:
-
根据区间关系合并结果:
- 如果 :两个区间分离, 在前, 在后
- 如果 :两个区间分离, 在前, 在后
- 否则:两个区间有重叠,合并为一个区间
-
输出区间内的所有整数
示例验证
例1:
- 不包含 的子段:由 组成,和的范围
- 包含 的子段:去掉 后剩下的 部分和范围为 ,加上 后为
- 两个区间 和 分离
- 结果: ✅
例2:
- 无不包含特殊元素(特殊元素不存在),
- 包含特殊元素的子段范围不存在,
- 结果: 内所有整数 ✅
例3:
- 不包含 的子段:
- 包含 的子段:去掉 后剩下 ,范围 ,加上 后为
- 两个区间 和 相邻( 和 连续),合并为
- 结果: ✅
时间复杂度
- 每个测试用例
- 总和 ,总复杂度
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; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } // 不包含特殊元素的子段和范围 int l1 = 0, r1 = 0; // 包含特殊元素的子段和范围 int l2 = 2e9, r2 = -2e9; int pr = 0; // 当前前缀和 int mnl = 0, mxl = 0; // 到当前位置的最小/最大前缀和 int mnr = 2e9, mxr = -2e9; // 特殊元素出现前的最小/最大前缀和 for (int i = 0; i < n; i++) { pr += a[i]; if (abs(a[i]) != 1) { // 遇到特殊元素,保存特殊元素前的范围 mnr = mnl; mxr = mxl; mnl = pr; mxl = pr; } // 更新不包含特殊元素的子段和范围 l1 = min(l1, pr - mxl); r1 = max(r1, pr - mnl); // 更新包含特殊元素的子段和范围 l2 = min(l2, pr - mxr); r2 = max(r2, pr - mnr); // 更新最小/最大前缀和 mnl = min(mnl, pr); mxl = max(mxl, pr); } vector<int> res; // 合并两个区间 if (l2 > r1) { // 区间分离:[l1, r1] 在前,[l2, r2] 在后 for (int i = l1; i <= r1; i++) res.push_back(i); for (int i = l2; i <= r2; i++) res.push_back(i); } else if (r2 < l1) { // 区间分离:[l2, r2] 在前,[l1, r1] 在后 for (int i = l2; i <= r2; i++) res.push_back(i); for (int i = l1; i <= r1; i++) res.push_back(i); } else { // 区间重叠,合并为一个连续区间 int L = min(l1, l2); int R = max(r1, r2); for (int i = L; i <= R; i++) res.push_back(i); } cout << res.size() << '\n'; for (int i = 0; i < (int)res.size(); i++) { cout << res[i] << " \n"[i == (int)res.size() - 1]; } } return 0; }关键点总结
- 区间连续性:由 组成的子段和构成连续区间
- 前缀和技巧: 的和 =
- 分类讨论:分含特殊元素和不含特殊元素两类
- 边界处理:空子段保证 一定在结果中
- 区间合并:两个连续区间的并集最多两个区间
- 1
信息
- ID
- 7063
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 5
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者