糖果传递(中位数、推公式、贪心)
题意
有\(n\)个人坐成一圈,每人有\(a[i]\)个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为\(1\)。求使所有人获得均等糖果的最小代价。
数据范围
\(1 \leq n \leq 1000000\)
\(0 \leq a[i] \leq 2 \times 10^9\)
思路
不妨设第\(n\)个人传给第\(1\)个人\(x_1\)个糖果(这里是净传递量,即第\(n\)个人传给第\(1\)个人的数量减去第\(1\)个人传给第\(n\)个人的数量),第\(1\)个人传给第\(2\)个人\(x_2\)个糖果,...,第\(n - 1\)个人传给第\(n\)个人\(x_n\)个糖果
中位数\(b = \frac{1}{n}(a_1 + a_2 + \dots + a_n)\),我们的目标是\(min(|x_1| + |x_2| + \dots + |x_n|)\)
因此我们可以根据数量关系,列出如下方程组:
\[a_1 - x_1 + x_n = b\\
a_2 - x_2 + x_1 = b\\
\dots\\
a_n - x_n + x_{n - 1} = b
\]
整理可得:
\[x_1 = x_n - (b - a_1)\\
x_2 = x_n - (2b - a_1 - a_2)\\
\dots\\
x_{n - 1} = x_n -((n - 1)b - a_1 - a_2 - \dots - a_{n - 1})\\
\]
因此我们的目标函数就转化为了\(|x_n - (b - a_1)| + |x_n - (2b - a_1 - a_2)| + \dots + |x_n - 0|\)
不妨设\(x = x_n\);\(c_1 = b - a_1, c_2 = 2b - a_1 - a_2, \dots ,c_n = 0\)
目标函数转化为\(|x - c_1| + |x - c_2| + \dots + |x - c_n|\)
这样就转化成了一个特别经典的问题,就是数轴上有\(n\)个点,问选哪个点到这\(n\)个点的距离之和最小。取中位数即可。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1000010;
int n;
ll a[N];
ll q[N];
int main()
{
scanf("%d", &n);
ll sum = 0;
for(int i = 1; i <= n; i ++) {
scanf("%lld", &a[i]);
sum += a[i];
a[i] += a[i - 1];
}
ll b = sum / n;
for(int i = 1; i <= n; i ++) {
q[i] = i * b - a[i];
}
sort(q + 1, q + n + 1);
ll ans = 0;
for(int i = 1; i <= n / 2; i ++) ans += q[n - i + 1] - q[i];
printf("%lld\n", ans);
return 0;
}

浙公网安备 33010602011771号