题解:洛谷 P6067 Moo Volume
【题目来源】
洛谷:P6067 [USACO05JAN] Moo Volume S - 洛谷
【题目描述】
Farmer John 的农场上有 \(N\) 头奶牛(\(1 \leq N \leq 10^5\)),第 \(i\) 头奶牛的位置为 \(x_i\)(\(0 \leq x_i \leq 10^9\))。
奶牛很健谈,每头奶牛都和其他 \(N-1\) 头奶牛聊天。第 \(i\) 头奶牛和第 \(j\) 头奶牛聊天时,音量为 \(|x_i-x_j|\)。
请您求出所有奶牛聊天音量的总和。
【输入】
第一行一个整数 \(N\)。
接下来 \(N\) 行,每行一个整数 \(x_i\)。
【输出】
输出总音量。
保证答案在 64 位带符号整数的表示范围内。
【输入样例】
5
1
5
3
2
4
【输出样例】
40
【核心思想】
-
问题分析:给定 \(N\) 头奶牛的位置 \(x_1, x_2, \dots, x_N\),求所有两两之间距离之和 \(\sum_{1 \leq i < j \leq N} |x_i - x_j|\)。直接枚举所有 \(O(N^2)\) 对不可行,需要利用排序后前缀和优化。
-
算法选择:
- 排序 + 前缀和递推:先排序,再利用有序性将绝对值去掉,通过递推公式 \(O(N)\) 计算总距离
-
关键步骤:
- 排序:将位置数组 \(a[1..N]\) 按升序排列,排序后 \(|a_i - a_j| = a_j - a_i\)(\(j > i\))
- 前缀和递推:
- 定义 \(\text{sa}[i]\) 为以 \(a_i\) 为右端点,与前面所有 \(i-1\) 个点的距离之和
- 递推公式:\(\text{sa}[i] = \text{sa}[i-1] + (a_i - a_{i-1}) \times (i-1)\)
- 解释:前 \(i-1\) 个点每个都比 \(a_{i-1}\) 小(或相等),它们到 \(a_i\) 的距离等于"到 \(a_{i-1}\) 的距离"加上" \(a_i - a_{i-1}\) ",共 \((i-1)\) 个点各加这段差值
- 累加答案:\(\text{ans} = \sum_{i=1}^{N} \text{sa}[i]\)(所有以 \(i\) 为右端点的距离和)
- 翻倍输出:由于 \(\text{sa}[i]\) 只统计了 \(j < i\) 的情况,而题目要求所有无序对,所以最终答案为 \(\text{ans} \times 2\)(或直接理解为每对被计算两次)
- 注意:实际上代码中 \(\text{ans}\) 累加的是 \(\text{sa}[i]\),而 \(\text{sa}[i]\) 已经是以 \(i\) 为右端点的所有左端点距离和,所以 \(\text{ans} = \sum_{i<j} (a_j - a_i)\),无需再乘 \(2\)。但代码输出 \(\text{ans} \times 2\),这是因为题目中"每头奶牛都和其他 \(N-1\) 头奶牛聊天"意味着有序对 \((i, j)\) 和 \((j, i)\) 都计算,所以总音量为 \(2 \times \sum_{i<j} |x_i - x_j|\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),排序 \(O(N \log N)\),递推 \(O(N)\)
- 空间复杂度:\(O(N)\),存储位置数组和前缀和数组
-
排序后前缀和优化的核心思想:
- 绝对值消去:排序后所有绝对值变为正向差值,避免分类讨论
- 增量递推:利用相邻元素的差值 \((a_i - a_{i-1})\) 乘以点数 \((i-1)\),将"每个新点与前面所有点的距离和"从 \(O(i)\) 优化到 \(O(1)\)
- 累积求和:\(\text{sa}[i]\) 记录以 \(i\) 为右端点的所有距离,全局累加即得所有无序对距离和
- 有序对翻倍:题目要求每对奶牛互相聊天(有序对),所以最终答案需乘 \(2\)
- 适用于数轴上点集距离和、一维聚类代价、曼哈顿距离投影类问题
【解题思路】

【算法标签】
普及 #数学
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义int为long long类型
#define int long long
// 定义数组最大长度
const int N = 100005;
// 变量定义
int n; // 数字的个数
int a[N]; // 存储输入的数字数组
int sa[N]; // 前缀和数组,sa[i]表示前i个数的某种累加值
int ans; // 最终结果
// 主函数,signed相当于int main,但因为#define int long long,所以用signed
signed main()
{
// 读入数字个数
cin >> n;
// 读入n个数字
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 对数组进行排序,从1到n
sort(a + 1, a + n + 1);
// 计算sa数组和ans
for (int i = 1; i <= n; i++)
{
// 计算sa[i]:当前数字与前一个数字的差值乘以(i-1),然后加上前一个sa值
sa[i] = sa[i - 1] + (a[i] - a[i - 1]) * (i - 1);
// 累加sa[i]到ans
ans += sa[i];
}
// 输出结果乘以2
cout << ans * 2 << endl;
return 0;
}
【运行结果】
5
1
5
3
2
4
40
浙公网安备 33010602011771号