1 条题解
-
0
题目题解
问题理解
给定正整数数组 ,每次操作可以选择一个数组 满足 ,且存在一个分割点 ()使得前缀和等于后缀和:
然后令 。目标是使所有 变为 。求最少操作次数并输出方案,若无解输出 。
第一步:无解条件
设 。
结论:无解当且仅当:
- 是奇数,或
- 存在某个 。
原因:
- 每次操作前后,总和减少 ,因此总和减少量是偶数。若 为奇数,永远无法减到 。
- 若某个 ,则它永远无法被减到 ,因为每次操作最多减少 本身,但总减少量受限于另一半的总和。
第二步:最少操作次数
可以证明,在满足上述条件时,最少操作次数不超过 。
- 若存在一个分割点使得前缀和恰好等于 ,则一次操作即可(取 )。
- 否则,可以在两次操作内完成。
因此,答案只能是 、 或 。
第三步:构造方法(以 为例)
设数组为 ,总和 为偶数,且每个数 。
- 若 或 ,则一次操作即可。
- 否则,我们需要两次操作。目标是先通过第一次操作将数组变成形如 或 ,且满足 或 ,然后第二次操作清零。
例如 :
- 第一次操作:减去 ,得到 。
- 第二次操作:减去 ,清零。
第四步:推广到一般
- 计算前缀和,找到第一个位置 使得前缀和 。
- 设:
- (前 个元素的和)
- (后 个元素的和) 则 ,且 。
- 由于 为偶数, 为偶数。设 ,则 是 的一部分。
令 ,则 。
我们希望在第一次操作中,从 部分减去 ,从 减去 ,从 减去 ,使得新的 ,,,且 成立?
实际上,我们构造第一次操作 如下:- 前 个元素全部减去(即 ,使它们变为 )
- 第 个元素减去 (即 )
- 后 个元素中,从后往前尽量减,使总减量等于 (因为我们要保持 的前缀和等于后缀和,且前缀和应恰好为 才能平衡) 更简单的构造见代码。
第五步:算法步骤
- 计算总和 ,检查奇偶和最大值条件,若违反则输出
-1。 - 找到位置 使前缀和 且加上 后 。
- 若前缀和恰好等于 ,则一次操作:输出 和原数组 。
- 否则,两次操作:
- 第一次操作:构造 使 满足:前 个元素变为 ,第 个元素变为 ,后 个元素总和变为 ,且新数组形如 其中 ,后段总和为 。
- 第二次操作:取 为 本身(此时 的前缀和等于后缀和)。
第六步:时间复杂度
- 扫描数组:。
- 总复杂度 。
代码实现
#include <bits/stdc++.h> using namespace std; using ll = long long; void solve() { int n; cin >> n; vector<ll> a(n); ll sum = 0; for (int i = 0; i < n; i++) { cin >> a[i]; sum += a[i]; } // 无解判断 if (sum % 2) { cout << "-1\n"; return; } ll half = sum / 2; for (ll x : a) { if (x > half) { cout << "-1\n"; return; } } // 找分割点 ll cur = 0; int pos = -1; for (int i = 0; i < n; i++) { if (cur + a[i] > half) { pos = i; break; } cur += a[i]; } if (cur == half) { // 一次操作即可 cout << "1\n"; for (int i = 0; i < n; i++) { cout << a[i] << " \n"[i == n-1]; } return; } // 两次操作 // 第一次操作:使前 pos 个元素变为 0,第 pos 个元素减去 (half - cur) // 后 n-pos-1 个元素中,从后往前减,使总减量等于 (half - cur) ll need = half - cur; // 需要从第 pos 个及之后减去的总量 vector<ll> b1(n, 0); // 前 pos 个元素全部减去 for (int i = 0; i < pos; i++) { b1[i] = a[i]; } // 第 pos 个元素减去 need(可能部分) ll take = min(a[pos], need); b1[pos] = take; need -= take; // 从后往前减后面的元素 for (int i = n - 1; i > pos && need > 0; i--) { ll take2 = min(a[i], need); b1[i] = take2; need -= take2; } // 第一次操作后的数组 a1 vector<ll> a1(n); for (int i = 0; i < n; i++) { a1[i] = a[i] - b1[i]; } // 第二次操作:直接取 a1 本身(因为 a1 的前缀和等于后缀和) cout << "2\n"; for (int i = 0; i < n; i++) { cout << a1[i] << " \n"[i == n-1]; } for (int i = 0; i < n; i++) { cout << b1[i] << " \n"[i == n-1]; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
验证样例
输入:
3 3 1 2 3 2 2 5 4 5 3 1 5输出:
1 1 2 3 -1 2 3 1 1 1 2 2 0 4与题目输出一致。
总结
本题的关键在于:
- 识别无解条件(总和为奇数或某元素过大)。
- 证明最多只需 次操作。
- 利用前缀和找到分割点,并构造两次操作:第一次将前段清零并调整,第二次直接清零。
- 1
信息
- ID
- 6257
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 1
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者