题解
题意概述:
- 找到一个点,使其到达每家商店的距离之和最小。
思路历程:
-
最开始没想那么多,直接暴力枚举,但是在第三个测试点上就超时了,看数据大小,1 \le N \le 10^5,0 \le A_i \le 40000,(还没有养成看数据大小的习惯)那么就是10^9的循环次数,会超时
-
然后我又假如了几个限制条件,如只从min(最小的坐标点)遍历到max,假如count>ml,直接break,但是这些条件还是没有从根本上优化10^9的复杂度
-
我也觉得应该就是中间的某个点是最短距离和的那个点,但是我又疑惑于怎么表达中间那个点,用什么区间来遍历.是算min和max的平均数,还是算a[mid],当时我觉得a[mid]会受到数据分布左右不平衡的影响,所以应该多遍历a[mid]左右的区间,但是这个区间的范围我也不知道怎么限定.我还想着能不能用其他什么算法来做,但也没做出来
-
最后,我才知道这道题求的就是所有坐标点的中位数
下面是AI给出的证明方法
1. 几何直观:分组博弈法
假设我们将所有的商店坐标按从小到大排序:A_1 \le A_2 \le \dots \le A_N。
我们把最外层的两家商店 A_1 和 A_N 看作一组。
- 如果要让货仓到这两家商店的距离之和 |x - A_1| + |x - A_N| 最小,货仓 x 应该建在哪里?
- 根据几何性质,只要 x 在 [A_1, A_N] 这个闭区间内,距离之和恒等于 A_N - A_1(即这段线段的长度)。
- 如果 x 跑到了 A_1 左边或 A_N 右边,距离之和一定会大于线段长度。
接下来,我们看次外层的两家商店 A_2 和 A_{N-1}:
- 同理,要让 |x - A_2| + |x - A_{N-1}| 最小,x 必须落在 [A_2, A_{N-1}] 之间。
结论:
为了让总距离之和最小,x 必须尽可能满足所有这些“嵌套区间”的约束。
-
当 N 为奇数时: 所有区间的交点只有一个,就是最中间的那个点 A_{(N+1)/2}。
-
当 N 为偶数时: 所有区间的交点是一个闭区间 [A_{N/2}, A_{N/2+1}]。在这个区间内的任意一点(包括端点)都能达到最小值。通常为了方便,我们取 A_{N/2} 或者中位数。
-
中位数恰恰是应对这种“不平衡”最强大的工具。
我们可以用 “拉力赛” 来想象:
-
假设你站在数轴上的某个点 x。
-
每一个位于你左边的商店,都想把你往左拉;每一个位于你右边的商店,都想把你往右拉。
-
如果你向右移动 1 厘米:
- 你离左边所有商店的距离都增加了 1 厘米。
- 你离右边所有商店的距离都减少了 1 厘米。
-
只要你左右两边的商店数量不等,比如左边有 10 家,右边有 20 家,那么你往右走,总距离一定会减少(减少了 20-10=10 厘米)。
-
只有当你走到一个点,使得 “左边的商店数 = 右边的商店数” 时,你无论往哪边走,总距离都会增加。
这就是为什么点的具体数值不重要,点的个数才重要。
-
-
平均数(Mean) 最小化的是距离的平方和 \sum (x - A_i)^2,这是最小二乘法的原理。
中位数(Median) 最小化的是绝对距离之和 \sum |x - A_i|,这在统计学中被称为 L_1 范数最小化。
代码:
#include <bits/stdc++.h>
using i64 = long long;
void solve(){
int n;
std::cin >> n;
std::vector<int> a(n,0);
for(int i = 0; i < n; ++i){
std::cin >> a[i];
}
std::sort(a.begin(),a.end());
int count = 0;
if(n & 1){
int temp = a[n >> 1];
for(int i = 0; i < n; ++i){
count += std::abs(temp - a[i]);
}
}
else{
for(int i = 0; i < n / 2; ++i){
count += a[n - 1 - i] - a[i];
}
}
std::cout << count;
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
solve();
return 0;
}
-
其实偶数中的配对思路奇数也受用,奇数中可以看成中间的那两个数是一个数了
-
更简洁的版本
#include <bits/stdc++.h> using i64 = long long; void solve(){ int n; std::cin >> n; std::vector<int> a(n, 0); for(int i = 0; i < n; ++i){ std::cin >> a[i]; } std::sort(a.begin(), a.end()); int count = 0; for(int i = 0; i < n / 2; ++i){ count += a[n - 1 - i] - a[i]; } std::cout << count; } int main(){ std::ios::sync_with_stdio(false); std::cin.tie(nullptr); solve(); return 0; }
新学知识点
位运算(注意运算优先级)
& 按位与
-
只有对应的两个二进位均为 1 时,结果位才为 1,否则为 0。
-
用法:
-
判断奇偶:
n & 1:计算机存储整数时,偶数的二进制最后一位必然是 0,奇数的最后一位必然是 1。所以当奇数时n & 1为真
偶数时n & 1为假
-
>> 按位右移
- 在处理正整数时,右移 k 位在数学上完全等同于除以 2^k 并向下取整。
- 用法:(n + 1) >> 1等价于(n + 1) / 2
总结启发
- 数学在算法竞赛中的作用???已经做到了一些题目与数学强相关或者结合数学有新方法