糖果传递(中位数、推公式、贪心)

题意

\(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;
}
posted @ 2021-02-04 20:20  pbc的成长之路  阅读(123)  评论(0)    收藏  举报