1 条题解
-
0
题目理解
我们有一个递增数列 a₁ ≤ a₂ ≤ ... ≤ aₙ。
一次操作:选一个位置 i(不能是第一个或最后一个),把 aᵢ 变成 aᵢ₋₁ + aᵢ₊₁ - aᵢ。
这个操作相当于交换相邻的差值:
- 设 dᵢ = aᵢ₊₁ - aᵢ
- 操作后,dᵢ₋₁ 和 dᵢ 交换位置
所以我们可以任意重排 d₁, d₂, ..., dₙ₋₁ 的顺序。
方差公式化简
方差 D = (1/n) × Σ(aᵢ - 平均)²
题目要求输出 n² × D 的最小值。
经过数学推导,可以化简为: n² × D = n × Σaᵢ² - (Σaᵢ)²
所以我们要最小化 n × Σaᵢ² - (Σaᵢ)²。
关键发现
经过推导发现,这个值只与差分的排列顺序有关,与 a₁ 无关。
设 Sₖ = aₖ - a₁(即前 k-1 个差分的和),那么: 目标值 = n × ΣSₖ² - (ΣSₖ)²
所以问题变成:如何排列差分,让这个值最小。
最优排列方式
通过分析,最优的排列是"单谷"形状:
- 把差分排序后,先放一个小的,然后放一个大的,再放一个小的...
- 或者反过来:先放大的,再放小的
- 形成"小大小大..."或"大小大小..."的交替模式
实际上,我们只需要尝试两种交替方式,取最小值即可。
算法步骤
- 读入数列,计算差分 d[i] = a[i+1] - a[i]
- 把差分从小到大排序
- 用双端队列构造两种序列:
- 方式1:奇数位置放左边,偶数位置放右边
- 方式2:偶数位置放左边,奇数位置放右边
- 对每种方式计算目标值,取最小值
代码实现
#include <iostream> #include <algorithm> #include <deque> #include <vector> using namespace std; typedef long long ll; int main() { int n; cin >> n; vector<ll> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } // 计算差分 vector<ll> d(n-1); for (int i = 0; i < n-1; i++) { d[i] = a[i+1] - a[i]; } // 排序差分 sort(d.begin(), d.end()); // 方法1:奇数下标放左边,偶数下标放右边 deque<ll> q1; q1.push_back(0); // S1 = 0 for (int i = 0; i < n-1; i++) { if (i % 2 == 0) { // 放右边 q1.push_back(d[i] + q1.back()); } else { // 放左边 q1.push_front(d[i] + q1.front()); } } // 计算目标值 ll sumS = 0, sumS2 = 0; for (ll x : q1) { sumS += x; sumS2 += x * x; } ll ans = n * sumS2 - sumS * sumS; // 方法2:偶数下标放左边,奇数下标放右边 deque<ll> q2; q2.push_back(0); for (int i = 0; i < n-1; i++) { if (i % 2 == 0) { // 放左边 q2.push_front(d[i] + q2.front()); } else { // 放右边 q2.push_back(d[i] + q2.back()); } } // 计算目标值 sumS = 0, sumS2 = 0; for (ll x : q2) { sumS += x; sumS2 += x * x; } ans = min(ans, n * sumS2 - sumS * sumS); cout << ans << endl; return 0; }
样例验证
样例1:
输入: 4 1 2 4 6 输出: 52与题目一致。
复杂度分析
- 排序差分:O(n log n)
- 构造序列:O(n)
- 计算答案:O(n)
- 总体:O(n log n),对于 n ≤ 10000 完全可行
这种方法利用了最优排列是单谷形状的性质,通过尝试两种交替方式找到最小方差。
- 1
信息
- ID
- 4214
- 时间
- 3000ms
- 内存
- 1024MiB
- 难度
- 10
- 标签
- 递交数
- 7
- 已通过
- 1
- 上传者