糖果传递

GitHub跳转原题关系图返回列表

环状均分糖果:设 c[i] 为 i-1 传给 i 的净流量,用平衡方程推出 c 是 a 减均值的前缀和,总代价为 Σ|c[i]-x|,排序取中位数即最小值。

OJ: roj

题目 ID: 3026

难度:普及+/提高-

标签:贪心中位数数学python

创建: 2026-10-01 10:51

更新: 2026-10-07 13:50

形式化题目

nn 个小朋友坐成一个环,第 ii 人有 aia_i 颗糖果。每次操作可以让某个人给相邻(左或右)的人传 1 颗糖果,代价为 1。保证 ∑ai\sum a_i 能被 nn 整除,求使所有人糖果数相等的最小总代价。

1⩽n⩽1061 \leqslant n \leqslant 10^6。

正解

思路

朴素做法及其瓶颈

最直接的想法是贪心地模拟传递:每次找一对"多的人"和"少的人",沿环上较短方向传糖果。但"较短方向"在环上与整体流量耦合——一个人的糖果可能绕圈流过多个中转者,每一步局部选择都影响后面的流向,模拟既难写对又容易陷入 O(n2)O(n^2) 甚至更高的复杂度,n⩽106n \leqslant 10^6 完全不可行。

关键转化:只数"经过的糖果",不模拟过程

无论传递如何进行,最终每人手里都是平均数 s=1n∑ais = \frac{1}{n}\sum a_i。定义 xix_i 为第 i−1i-1 个人(沿环逆时针)净传给第 ii 个人的糖果数:xi>0x_i > 0 表示 i−1i-1 传给 ii,xi<0x_i < 0 表示 ii 反向传给 i−1i-1。一次跨越 kk 条边的传递可以拆成 kk 次相邻传递,代价相同,所以只需统计每条边上净流过多少颗糖果。

第 ii 个人的糖果守恒:收到 xix_i,送出 xi+1x_{i+1},初始 aia_i,最终 ss,于是

ai+xi−xi+1=s⟹xi+1=xi+ai−s.a_i + x_i - x_{i+1} = s \quad\Longrightarrow\quad x_{i+1} = x_i + a_i - s.

这是一个递推式:整条环的流量只由自由变量 x1x_1(第 nn 人给第 11 人的净流量)决定。令 c0=0c_0 = 0,ci=ci−1+ai−sc_i = c_{i-1} + a_i - s(即 ai−sa_i - s 的前缀和),逐层代入得

xi+1=x1+ci(i=0,1,…,n−1).x_{i+1} = x_1 + c_i \qquad (i = 0, 1, \dots, n-1).

注意到 cn=∑ai−ns=0=c0c_n = \sum a_i - ns = 0 = c_0,所以集合 {c0,c1,…,cn−1}\{c_0, c_1, \dots, c_{n-1}\} 与 {c1,…,cn}\{c_1, \dots, c_n\} 恰好相同,总代价为各边净流量的绝对值之和:

cost(x1)=∑i=1n∣xi∣=∑i=1n∣ci+x1∣.\text{cost}(x_1) = \sum_{i=1}^{n} |x_i| = \sum_{i=1}^{n} |c_i + x_1|.

问题变成:在数轴上选一个点 −x1-x_1,最小化它到 nn 个点 c1,…,cnc_1, \dots, c_n 的距离之和——经典的中位数问题。−x1-x_1 取 cc 的中位数时代价最小;任何合法传递方案都对应某个 x1x_1,反之取中位数得到的流量也自动满足守恒方程,因此这个下界可以取到。

用样例验证

以样例 a=[1,2,5,4]a = [1, 2, 5, 4] 为例,s=3s = 3:

人 ii 1 2 3 4
初始 aia_i 1 2 5 4
ai−sa_i - s −2-2 −1-1 22 11
cic_i(前缀和) −2-2 −3-3 −1-1 00
排序后 −3-3 −2-2 −1-1 00

排序后中位数在 −2-2 与 −1-1 之间,任取 x1=2x_1 = 2,代入 xi+1=x1+cix_{i+1} = x_1 + c_i 得各边净流量

边(谁给谁) 4→14 \to 1 1→21 \to 2 2→32 \to 3 3→43 \to 4
净流量 xix_i 22 00 −1-1 11

即 4 号给 1 号 2 颗,3 号给 2 号、3 号给 4 号各 1 颗(负号表示反向),总代价 2+0+1+1=42+0+1+1 = 4,与样例输出一致。逐人验证(收到 xix_i、送出 xi+1x_{i+1}):1+2−0=31+2-0=3、2+0−(−1)=32+0-(-1)=3、5+(−1)−1=35+(-1)-1=3、4+1−2=34+1-2=3,全部等于平均数 3。有 5 颗糖的 3 号把糖果沿环向两侧分流,各边流量绝对值之和恰好是各点 cic_i 到中位数的距离。

于是算法只有三步:求 ss、做一遍前缀和得到 cc、排序取中位数求绝对值和。n=106n = 10^6 时排序是主要开销,完全可接受。

代码

Python 版:

python
import sys

def main() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    a = [next(data) for _ in range(n)]
    s = sum(a) // n                       # 目标:每人最终 s 颗
    from itertools import accumulate
    # c[i] = a[i]-s + c[i-1]:即 i-1 传给 i 的净糖果数(负表示反向传)
    c = list(accumulate(x - s for x in a))
    c.sort()                              # 代价 = Σ|c[i]-x|,x 取中位数最优
    m = c[n // 2]
    print(sum(abs(v - m) for v in c))

main()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 14:42
 * update_at: 2026-10-06 14:42
 */

// 糖果传递:环上均分糖果,最小化相邻传递总代价。
// 设 c[i] 为前 i 个人的 (a[i]-s) 前缀和,即第 i 人传给第 i+1 人的净流量
//(负号表示反向传),总代价 = Σ|c[i] - x|,x 取 c 的中位数时最小。

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1000005;

typedef long long ll;

ll n;
ll a[MAXN]; // a[i]:第 i 个小朋友的初始糖果数
ll c[MAXN]; // c[i]:(a[i]-s) 的前缀和,即边上净流量关于 x_1 的偏移

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    ll sum = 0;
    for (ll i = 1; i <= n; i++) {
        cin >> a[i];
        sum += a[i];
    }
    ll s = sum / n; // 最终每人手里的糖果数(数据保证有解,即 n 整除 sum)

    // 前缀和:c[i] = c[i-1] + a[i] - s,第 n 项恰好回到 0(环闭合)
    for (ll i = 1; i <= n; i++) {
        c[i] = c[i - 1] + a[i] - s;
    }

    // 总代价 = Σ|c[i] + x_1|,数轴上选 -x_1 到各点距离和最小 -> 取中位数
    sort(c + 1, c + n + 1);
    ll mid = c[(n + 1) / 2]; // 中位数(n 为奇数取正中,偶数取任意一个皆最优)

    ll ans = 0;
    for (ll i = 1; i <= n; i++) {
        ans += llabs(c[i] - mid);
    }
    cout << ans << endl;

    return 0;
}

复杂度

  • 时间复杂度 O(nlog⁡n)O(n \log n):一遍前缀和 O(n)O(n),排序 O(nlog⁡n)O(n \log n)(也可用 nth_element 类思想做到期望 O(n)O(n))。
  • 空间复杂度 O(n)O(n):存储数组 aa 和前缀和数组 cc。

总结

  • 环上均分问题不要模拟传递过程,而是给每条边设净流量变量 xix_i,用守恒方程 xi+1=xi+ai−sx_{i+1} = x_i + a_i - s 把所有流量表示成 x1x_1 的线性函数。
  • 代数变形后总代价是 ∑∣ci+x1∣\sum |c_i + x_1|,其中 cic_i 是 ai−sa_i - s 的前缀和——环问题就此变成数轴上的"选一点到各点距离和最小",即中位数贪心。
  • 三个可复用的套路:把长距离传递拆成相邻传递(代价不变)、环上流量只有一个自由变量、绝对值和的最优解在中位数。