糖果传递
有 $n$ 个小朋友坐成一圈,每人有 $a \left[ i \right]$ 个糖果。
每人只能给左右两人传递糖果。
每人每次传递一个糖果代价为 $1$。
求使所有人获得均等糖果的最小代价。
输入格式
第一行输入一个正整数 $n$,表示小朋友的个数。
接下来 $n$ 行,每行一个整数 $a \left[ i \right],表示第 $i$ 个小朋友初始得到的糖果的颗数。
输出格式
输出一个整数,表示最小代价。
数据范围
$1 \leq n \leq 1000000$,
$0 \leq a \left[ i \right] \leq 2 \times 10^{9}$,
数据保证一定有解。
输入样例:
4 1 2 5 4
输出样例:
4
解题思路
很难想到这是一道数学题。
设$x_{i}$为第$i$个人给前一个人($i - 1$。其中当$i = 1$时,前一个人为第$n$个人)的糖果数量。注意,这里的$x_{i}$可以是正数或者负数。当$x_{i}$为正数,表示第$i$个人给前一个人糖果;当$x_{i}$为负数,表示前一个人给第$i$个人糖果。
因此整一个代价为$\left| x_{1} \right| + \left| x_{2} \right| + \cdots + \left| x_{n} \right|$,我们的目标就是求$\left| x_{1} \right| + \left| x_{2} \right| + \cdots + \left| x_{n} \right|$的最小值。
设$\overset{-}{a}$为所有糖果的平均数,最后每个人手上的糖果数量应该为$\overset{-}{a}$,列方程:
\begin{cases}
a_{1} - x_{1} + x_{2} &= \overset{-}{a} \\
a_{2} - x_{2} + x_{3} &= \overset{-}{a} \\
&\vdots \\
a_{n - 1} - x_{n - 1} + x_{n} &= \overset{-}{a} \\
a_{n} - x_{n} + x_{1} &= \overset{-}{a} \\
\end{cases}
发现秩不为$n$,方程有无穷多解。所有式子加起来会得到常数等于常数,即有$n - 1$个独立的方程,$n$个未知数。所以存在一个*元,可以用某一个$x_{i}$来表示其他所有的$x$。
移项,把上述方程变为
\begin{cases}
x_{1} - x_{2} &= a_{1} - \overset{-}{a} \\
x_{2} - x_{3} &= a_{2} - \overset{-}{a} \\
&\vdots \\
x_{n - 1} - x_{n} &= a_{n - 1} - \overset{-}{a} \\
x_{n} - x_{1} &= a_{n} - \overset{-}{a} \\
\end{cases}
从最后一个方程开始,每次都往前加上一个方程,得到
\begin{cases}
x_{1} &= x_{1} - 0 \\
x_{2} &= x_{1} - \left( {\left( {n - 1} \right) \cdot \overset{-}{a} - a_{2} - a_{3} - \cdots - a_{n}} \right) \\
&\vdots \\
x_{n - 1} &= x_{1} - \left( {2 \cdot \overset{-}{a} - a_{n - 1} - a_{n}} \right) \\
x_{n} &= x_{1} - \left( {\overset{-}{a} - a_{n}} \right) \\
\end{cases}
这样我们就可以用$x_{1}$来表示其他所有的$x$了。
代入$\left| x_{1} \right| + \left| x_{2} \right| + \cdots + \left| x_{n} \right|$中,有$$\left| {x_{1} - 0} \right| + \left| {x_{1} - \left( {\left( {n - 1} \right) \cdot \overset{-}{a} - a_{2} - a_{3} - \cdots - a_{n}} \right)} \right| + \cdots + \left| {x_{1} - \left( {\overset{-}{a} - a_{n}} \right)} \right|$$对于上面中的每一项,我们可以发现$x_{1}$都是减去一个常数,因此可以写成$$\left| {x_{1} - c_{1}} \right| + \left| {x_{2} - c_{2}} \right| + \cdots + \left| {x_{n} - c_{n}} \right|$$
我们可以惊奇的发现,要求上式的最小值,等价于要找到一个$x_{1}$,使得$x_{1}$分别到$c_{1} \sim c_{n}$的距离的和最小。
当$x_{1}$取到$c_{1} \sim c_{n}$的中位数时,上式就可以取到最小值。这个模型可以参考货仓选址这一题。这里简单证明一下。
先假定$c_{1} \leq c_{2} \leq \cdots \leq c_{n}$。
我们把式子$$\left| {x_{1} - c_{1}} \right| + \left| {x_{2} - c_{2}} \right| + \cdots + \left| {x_{n} - c_{n}} \right|$$的每一项分成若干组,第$1$项和第$n$项为一组,第$2$项和第$n - 1$项为一组,以此类推,如果$n$为奇数,那么第$\left\lceil \frac{n}{2} \right\rceil$项就独自为一组,得到$$\left( {\left| {x_{1} - c_{1}} \right| + \left| {x_{1} - c_{n}} \right|} \right) + \left( {\left| {x_{1} - c_{2}} \right| + \left| {x_{1} - c_{n - 1}} \right|} \right) + \cdots$$由绝对值不等式$\left| {\left| a \right| - \left| b \right|} \right| \leq \left| {a - b} \right| \leq \left| a \right| + \left| b \right|$,得到$$\left| {x_{1} - c_{1}} \right| + \left| {x_{1} - c_{n}} \right| \geq \left| {x_{1} - c_{1} - \left( {x_{1} - c_{n}} \right)} \right| = \left| {c_{n} - c_{1}} \right|$$意味着当$x_{1} \in \left\lbrack {c_{1}, ~c_{n}} \right\rbrack$,才取到等号。对于式子的其他组,同理可得。最后式子变为$$\left( {\left| {x_{1} - c_{1}} \right| + \left| {x_{1} - c_{n}} \right|} \right) + \left( {\left| {x_{1} - c_{2}} \right| + \left| {x_{1} - c_{n - 1}} \right|} \right) + \cdots \geq \left| {c_{n} - c_{1}} \right| + \left| {c_{n - 1} - c_{2}} \right| + \cdots$$我们可以发现,当每一组都取到等号,上式才可以取到最小值。因为我们一开始规定了$c_{1} \leq c_{2} \leq \cdots \leq c_{n}$,因此可以发现要给每一组都取到等号,$x_{1}$的取值区间都在不断缩小,要一直到最后一组都要满足。可以发现就是取$c_{1} \sim c_{n}$的中位数(如果$n$为奇数,中位数就是$c_{\left\lceil \frac{n}{2} \right\rceil}$,偶数的话,取$c_{\frac{n}{2}}$或$c_{\frac{n}{2} + 1}$都可以)。
当然,我们还可以从几何意义去考虑,可以发现只有当$x_{1}$在$c_{1} \sim c_{n}$这个区间内,$x_{1}$到$c_{1}$与$c_{n}$的距离之和才会最小,其他组也是这样分析,最后得到的结论相同。
最后我们来看看$c_{i}$是怎么求的。
看回上面的方程组,可以发现$c_{n} = {\overset{-}{a} - a_{n}}$,$c_{n - 1} = 2\overset{-}{a} - a_{n - 1} - a_{n} = c_{n} - \overset{-}{a} - a_{n - 1}$,......,$c_{1} = c_{2} - \overset{-}{a} - a_{1} = 0$。
可以发现递推式$c_{i} = c_{i + 1} - \overset{-}{a} - a_{i}$。
AC代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int N = 1e6 + 10; 6 7 int a[N]; 8 long long c[N]; 9 10 int main() { 11 int n; 12 scanf("%d", &n); 13 14 long long sum = 0; 15 for (int i = 1; i <= n; i++) { 16 scanf("%d", a + i); 17 sum += a[i]; 18 } 19 20 int avg = sum / n; 21 22 // 通过递推式求ci 23 for (int i = n; i; i--) { 24 c[i] = c[i + 1] - avg + a[i]; 25 } 26 27 sort(c + 1, c + n + 1); // 排序,使得ci是递增的顺序。由于ci的位置不会影响答案,因此使得ci为递增的序列,方便求中位数 28 29 long long ret = 0; 30 for (int i = 1; i <= n + 1 >> 1; i++) { 31 ret += c[n - i + 1] - c[i]; // 最小值就是每个点到中位数的距离的和,也就是每组区间的长度的和 32 } 33 printf("%lld", ret); 34 35 return 0; 36 }
参考资料
AcWing 122. 糖果传递(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/723/