题目描述
在一条数轴上有
N
N
N 家商店,它们的坐标分别为
A
1
A_1
A1~
A
N
A_N
AN。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
输入格式
第一行输入整数N。
第二行N个整数
A
1
A_1
A1~
A
N
A_N
AN。
输出格式
输出一个整数,表示距离之和的最小值。
数据范围
1 ≤ N ≤ 100000 1 \le N \le 100000 1≤N≤100000
输入样例:
4
6 2 9 1
输出样例:
12
题解
我们用直觉可以选出在 范围内最中间的点 到各个点的距离之和最短,这是对的。
也就是先排序,然后求出中位数,计算一遍距离和即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 100000 + 10;
int n, A[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++)
cin >> A[i];
sort(A + 1, A + 1 + n);
int p = A[n / 2 + 1], ans = 0;
for (int i = 1; i <= n; i ++)
ans += abs(A[i] - p);
cout << ans;
return 0;
}
但在考场上,我们因为无法证明该贪心的正确性而不敢下手怎么办?虽然这题写一遍上述程序也不会耗时太多。
这时候我们可以写暴力,该题中没有给出点的取值范围,经过测试不是很大,暴力用的时间也不太多。
看一个数据
1 2 3 5 6 7
如果我们要选一个点到各个点的距离最短,那么需要选4,模拟一遍4到各个点的距离之和怎么算。
先算4的左边
(
4
−
1
)
+
(
4
−
2
)
+
(
4
−
3
)
=
4
−
1
+
4
−
2
+
4
−
3
=
4
+
4
+
4
−
1
−
2
−
3
=
4
∗
3
−
(
1
+
2
+
3
)
(4 - 1) + (4 - 2) + (4 - 3) \\ = 4 - 1 + 4 - 2 + 4 - 3 \\ = 4 + 4 + 4 - 1 - 2 - 3 \\ = 4 * 3 - (1 + 2 + 3)
(4−1)+(4−2)+(4−3)=4−1+4−2+4−3=4+4+4−1−2−3=4∗3−(1+2+3)
通过观察我们可以发现 选4之后计算距离和,其实就是计算: 4(当前位置) * 左边数的个数 - 左边数的和
快速求和我们可以先预处理出前缀和来解决。
再来看看4的右边
(
5
−
4
)
+
(
6
−
4
)
+
(
7
−
4
)
=
5
−
4
+
6
−
4
+
7
−
4
=
5
+
6
+
7
−
4
−
4
−
4
=
5
+
6
+
7
−
4
∗
3
(5 - 4) + (6 - 4) + (7 - 4) \\ = 5 - 4 + 6 - 4 + 7 - 4 \\ = 5 + 6 + 7 - 4 - 4 - 4 \\ = 5 + 6 + 7 - 4 * 3
(5−4)+(6−4)+(7−4)=5−4+6−4+7−4=5+6+7−4−4−4=5+6+7−4∗3
右边同理,观察得到公式为: 右边数的和 - 4(当前位置) * 右边数的个数
所以我们把左右两边加起来就能得到当前位置到各个位置的距离和了。
#include <bits/stdc++.h>
using namespace std;
const int N = 100000 + 10;
int n, A[N], lefts[N], rights[N];
int getleft(int x) //用二分快速寻找数的个数 通过下标(地址)相减就能快速求得个数
{
auto p = lower_bound(A + 1, A + 1 + n, x);
if (*p == x) return p - A; //一些特殊情况需要特判一下
return p - A - 1;
}
int getright(int x)
{
auto p = lower_bound(A + 1, A + 1 + n, x);
if (*p == x) return A + n - p + 1;
return A + n - p + 1;
}
int main()
{
cin >> n;
int Min = INT_MAX, Max = INT_MIN;
for (int i = 1; i <= n; i ++)
{
cin >> A[i];
Min = min(Min, A[i]);
Max = max(Max, A[i]);
}
sort(A + 1, A + 1 + n);
for (int i = 1; i <= n; i ++) lefts[i] = lefts[i - 1] + A[i]; //前缀和
for (int i = n; i >= 1; i --) rights[i] = rights[i + 1] + A[i]; //后缀和
int ans = INT_MAX;
for (int i = Min; i <= Max; i ++) //从最小到最大的数进行枚举
{
int lcnt = getleft(i), rcnt = getright(i); //左右两边数的个数
int lsum = lcnt * i - lefts[lcnt], rsum = rights[n - rcnt + 1] - rcnt * i; //计算当前位置到左边的距离和 和 当前位置到右边的距离和,具体推导过程看上面。
ans = min(ans, lsum + rsum);
}
cout << ans;
return 0;
}