货仓选址
问题描述
在一条数轴上有 \(N\) 家商店,它们的坐标分别为 \(A_1~A_N\)。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
问题分析
首先考虑只有两家商店,显然货仓选在中间为最优解,放在两边路程总会有叠加的部分存在。若有三家商店,显然货仓选在中间的商店为最优解。
根据上面的思路,我们很容易得到推广:偶数家商店,货仓选在中间为最优解,奇数家商店,货仓选在中间一家商店,两侧为偶数家商店为最优解
同时上述思想也可以描述为货仓选择在中间点为最优解,从而可以使用快速选择或nth_element找到中间值,累加各点到中间点距离绝对值求解
法1: 利用性质计算
时间复杂度: \(O(nlog_n)\),因为用到了sort()
思路
这是我自己想的写法,我想的是对于一个区间的两个端点,货仓选在中间一定比选择在端点两侧情况更优
同时,中间的点无论选择在哪里,到两个端点的距离和一定是固定的,都是区间长度
所以对于偶数个数,最优答案就是货仓选在最中间,两边的商店数量一致,一前一后数值相减叠加到答案即可
对于奇数个个数,不考虑中间点,两边的偶数个点的策略和上面的方案一致,对于中间点,货仓选择在此点答案能够取得最小值
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
int main()
{
cin >> n;
for (int i = 0; i < n; ++ i) cin >> a[i];
sort(a, a + n);
int res = 0;
int i = -1, j = n;
while (++ i < -- j) res += a[j] - a[i]; // 类似快排的写法
cout << res << endl;
return 0;
}
法2: 找到中间元素-sort()
时间复杂度: \(O(nlog_n)\),因为用到了sort()
/**
* 第一种方法中我并没有真的找到货仓的位置,而是利用性质直接算出最终答案
* 此方法就是确确实实找到了货仓的位置,然后遍历一遍序列把所有商店到货仓的位置进行叠加获得答案
*/
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
int main()
{
cin >> n;
for (int i = 0; i < n; ++ i) cin >> a[i];
sort(a, a + n);
int res = 0;
for (int i = 0; i < n; ++ i) res += abs(a[i] - a[n >> 1]);
cout << res << endl;
return 0;
}
法3: 找到中间元素-nth_element
时间复杂度: \(O(n)\),nth_element是O(n)
/**
* 上面的两种做法都是利用的sort,时间复杂度都是O(nlogn)
* 假设在上面一种做法中,我们并没有得到一个有序序列,但是我们知道在有序序列中的中间位置的数是多少,此时,也是可以做的,也就是说我们排序的目的本质上来说只是为了找到中间那个元素是多少
* 针对这个问题,我们可以避免使用O(nlogn)的sort,而采用O(n)的快速选择算法找到第((n >> 1) + 1)小的值(这里说的是第几小而非数组下标),也就找到了有序序列中的中间元素
* 在此代码中,我们采用现有的stl函数nth_element快速将第((n >> 1) + 1)小的数放到正确的位置,实际上是数组下标为(n >> 1)的数(从0开始)
* nth_element的用法见这里:
*/
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
int main()
{
cin >> n;
for (int i = 0; i < n; ++ i) cin >> a[i];
nth_element(a, a + (n >> 1), a + n);
int res = 0;
for (int i = 0; i < n; ++ i) res += abs(a[i] - a[n >> 1]);
cout << res << endl;
return 0;
}
法4: 找到中间元素-手写快速选择
时间复杂度: \(O(n)\),快速选择的时间复杂度
/**
* 上面的nth_element是把第((n >> 1) + 1)小的数真的放到了对应的位置,但是实质上我们只是要找到中间位置的元素值是多少,并不需要让它真的到那个位置
* 所以,我们可以采用手写快速选择找到第((n >> 1) + 1)小的数
*/
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
int qsort(int a[], int l, int r, int k) // 纯模板
{
if (l == r) return a[l];
int x = a[l + r >> 1], i = l - 1, j = r + 1;
while (i < j)
{
while (a[++ i] < x);
while (a[-- j] > x);
if (i < j) swap(a[i], a[j]);
}
int sl = j - l + 1;
if (k <= sl) return qsort(a, l, j, k);
return qsort(a, j + 1, r, k - sl);
}
int main()
{
cin >> n;
for (int i = 0; i < n; ++ i) cin >> a[i];
int u = qsort(a, 0, n - 1, (n >> 1) + 1); // 这里如果用移位需要注意>>和+的优先级
int res = 0;
for (int i = 0; i < n; ++ i) res += abs(a[i] - u);
cout << res << endl;
return 0;
}