题解:学而思编程 货仓选址2

【题目来源】

学而思编程:货仓选址2

【题目描述】

在一条数轴上有 \(n\) 家商店,它们的坐标分别为 \(a_1\)\(a_n\)

现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。根据运送的距离和商品的重量,所需的运费也不一样。向距离 \(d\) 公里的地方运送 \(W\) 公斤的商品所需的运费是 \(W×d\)

运送到第 \(i\) 家商店的商品的重量是 \(w_i\)

为了提高效率,求把货仓建在何处,可以使得运送所有商品所需运费之和最小。

【输入】

第一行输入整数 \(n\)

第二行 \(n\) 个整数 \(a_1\)\(a_n\)

第三行 \(n\) 个整数 \(w_1\)\(w_n\)

【输出】

输出一个整数,表示运费之和的最小值。

【输入样例】

4
6 2 9 1
1 2 3 4

【输出样例】

29

【核心思想】

  1. 问题分析:给定 \(n\) 个商店在数轴上的坐标 \(a_1, a_2, \ldots, a_n\) 和对应的商品重量 \(w_1, w_2, \ldots, w_n\),需要选择一个位置建立货仓,使得运送所有商品所需运费之和最小。运费计算公式为 \(\sum_{i=1}^{n} w_i \times |a_i - x|\),其中 \(x\) 为货仓位置。这是一个加权中位数贪心问题,关键在于最优位置是加权中位数。

  2. 算法选择

    • 加权中位数性质:对于带权重的货仓选址问题,最优位置是加权中位数,即满足前缀加权和 \(\geq\) 总加权和的一半的位置
    • 数学证明:设选址位置为 \(x\),总成本为 \(f(x) = \sum_{i=1}^{n} w_i \times |a_i - x|\)。当 \(x\) 为加权中位数时,\(f(x)\) 取得最小值
    • 前缀和判定:按距离排序后,维护前缀加权和,当 tot >= sum - tot 时,当前位置即为加权中位数
  3. 关键步骤

    • 读取输入\(n\)(商店数量)、\(a[1..n]\)(商店坐标)、\(w[1..n]\)(商品重量)
    • 计算总加权和sum = Σw[i] * a[i]
    • 排序:按距离从小到大排序
    • 找加权中位数(遍历 \(i\)\(1\)\(n\)):
      • 累加前缀和tot += a[i].w * a[i].d
      • 判定中位数:如果 tot >= sum - tot,则当前位置 \(a[i].d\) 为加权中位数
    • 计算总成本:以加权中位数为选址位置,计算所有点到该位置的加权距离之和
    • 输出结果:最小运费之和 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),主要是排序复杂度
    • 空间复杂度:\(O(n)\),存储商店信息
  5. 加权中位数贪心的核心思想

    • 加权中位数最优性:在带权重的情况下,加权中位数是使得到所有点加权距离之和最小的位置
    • 前缀和判定:通过维护前缀加权和,找到满足平衡条件的位置
    • 贪心正确性:加权中位数使得左右两侧的加权和尽可能平衡,从而最小化总距离成本
    • 与基础中位数的区别:基础中位数只考虑点的数量,加权中位数考虑点的权重
    • 适用于带权选址问题、加权距离最小化、资源分配优化等问题

【算法标签】

贪心

【代码详解】

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;

int n, sum, ans;  // sum: 总加权和, ans: 最小总成本
struct node
{
    int w, d;  // w: 权重, d: 距离
} a[N];

// 按距离从小到大排序
bool cmp(node x, node y)
{
    return x.d < y.d;
}

signed main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i].d;
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i].w;
        sum += a[i].w * a[i].d;  // 计算总加权和
    }
    sort(a + 1, a + n + 1, cmp);

    int tot = 0;  // 前缀加权和
    for (int i = 1; i <= n; i++)
    {
        tot += a[i].w * a[i].d;
        // 当前缀加权和大于等于总加权和的一半时
        if (tot >= sum - tot)
        {
            int pos = a[i].d;  // 选择当前位置作为最优位置
            for (int j = 1; j <= n; j++)
            {
                ans += abs(a[j].d - pos) * a[j].w;  // 计算总成本
            }
            break;
        }
    }
    cout << ans;
    return 0;
}

【运行结果】

4
6 2 9 1
1 2 3 4
29
posted @ 2026-06-14 10:47  团爸讲算法  阅读(15)  评论(0)    收藏  举报