题解:洛谷 P4016 负载平衡问题

【题目来源】

洛谷:P4016 负载平衡问题 - 洛谷

【题目描述】

G 公司有 \(n\) 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 \(n\) 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。

【输入】

第一行一个正整数 \(n\),表示有 \(n\) 个仓库。

第二行 \(n\) 个正整数,表示 \(n\) 个仓库的库存量。

【输出】

一行一个非负整数,输出最少搬运量。

【输入样例】

5
17 9 14 16 4

【输出样例】

11

【核心思想】

  1. 问题分析:给定 \(n\) 个环形排列的仓库,每个仓库存储 \(a_i\) 数量的货物,只能在相邻仓库之间搬运货物。求使所有仓库库存相同的最小搬运量。这是一个贪心 + 中位数问题,关键在于将环形问题转化为线性问题,并利用中位数性质最小化绝对值之和。

  2. 算法选择

    • 贪心传递:从第一个仓库开始,依次将每个仓库调整到平均值,将差额传递给下一个仓库
    • 中位数优化:记录每个仓库的累计传递量,以中位数为基准计算最小搬运量
    • 偏移量枚举:尝试正负偏移量,找到使总搬运量最小的基准点
  3. 关键步骤

    • 计算平均值avg = sum / n,即每个仓库最终应有的库存量
    • 贪心传递\(i = 1\)\(n-1\)):
      • 计算第 \(i\) 个仓库需要调整的数量:t = avg - a[i]
      • 将第 \(i\) 个仓库调整为平均值:a[i] = avg
      • 从下一个人处调整:a[i + 1] -= t
      • 记录传递量:b[i + 1] = t
      • 累加操作次数:ans += abs(t)
    • 中位数优化
      • 以当前传递量为基础,尝试正偏移 calc(i) 和负偏移 calc(-i)
      • 当操作次数增加时停止,更新最小搬运量
    • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),贪心传递 \(O(n)\),偏移量枚举 \(O(n)\)
    • 空间复杂度:\(O(n)\),存储库存数组和传递记录数组
  5. 贪心传递与中位数的核心思想

    • 环形转线性:固定一个仓库为起点,将环形问题转化为线性传递问题
    • 局部最优:每次将一个仓库调整到平均值,将差额传递给下一个仓库,保证局部最优
    • 累计传递量b[i] 表示第 \(i\) 个仓库从前面累计接收的货物量,以中位数为基准最小化 sum(|b[i] + x|)
    • 中位数性质:使绝对值之和最小的点是中位数,通过枚举偏移量找到最优基准点
    • 适用于环形均分、最小搬运量、负载平衡类问题

【算法标签】

普及+ #贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int n, sum, ans;  // n: 人数, sum: 糖果总数, ans: 最少操作次数
int a[N], b[N];  // a: 初始糖果数, b: 传递记录

// 计算偏移量为x时的操作次数
int calc(int x)
{
    int res = 0;
    for (int i = 1; i <= n; i++)
    {
        res += abs(b[i] + x);  // 计算新的传递量
    }
    return res;
}
int main()
{
    cin >> n;  // 输入人数
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum += a[i];  // 计算总糖果数
    }
    int avg = sum / n;  // 平均每人糖果数

    // 贪心传递,使每个人达到平均数
    for (int i = 1; i < n; i++)
    {
        int t = avg - a[i];  // 第i人需要调整的数量
        a[i] = avg;  // 第i人调整为平均数
        a[i + 1] -= t;  // 从下一个人处调整
        b[i + 1] = t;  // 记录传递量
        ans += abs(t);  // 累加操作次数
    }

    // 尝试正偏移
    for (int i = 1;; i++)
    {
        int res = calc(i);
        if (res > ans)  // 如果操作次数增加,停止
        {
            break;
        }
        else
        {
            ans = res;  // 更新最少操作次数
        }
    }
    // 尝试负偏移
    for (int i = 1;; i++)
    {
        int res = calc(-i);
        if (res > ans)
        {
            break;
        }
        else
        {
            ans = res;
        }
    }

    cout << ans << endl;  // 输出最少操作次数
    return 0;
}

【运行结果】

5
17 9 14 16 4
11
posted @ 2026-06-25 09:15  团爸讲算法  阅读(10)  评论(0)    收藏  举报