题解:AcWing 4921 食品配送

【题目来源】

AcWing:4921. 食品配送 - AcWing题库

【题目描述】

数轴上有 \(N\) 个人,其中第 \(i\) 个人所在的位置坐标为 \(x_i\)

数轴上还有一个外卖餐厅,坐标位置为 \(X\)

一天的午餐时间,\(N\) 个人同时从餐厅点餐。

作为餐厅员工,你需要带着所有人的午饭从餐厅出发,将每个人点的饭送至其所在的位置。

你的移动速度是每分钟移动 \(V^{−1}\) 单位距离。

每个人在刚点餐时的不满值均为 \(0\)

对于第 \(i\) 个人,在其拿到自己的午饭之前,每过一分钟,他就会增加 \(b_i\) 的不满值。

一个人的最终不满值就是其拿到自己的午饭时的不满值。

请你合理设计配送路线以及配送方案,使得所有人的最终不满值之和尽可能小。

请你输出这个最小可能值。

【输入】

输入包含多组测试数据。

每组数据第一行包含三个整数 \(N,V,X\)

接下来 \(N\) 行,每行包含两个整数 \(x_i,b_i\)

【输出】

每组数据输出一行结果,一个整数,表示所有人的最终不满值之和的最小可能值。

【输入样例】

5 1 0
1 1
2 2
3 3
4 4
5 5

【输出样例】

55

【核心思想】

  1. 问题分析:给定 \(N\) 个人在数轴上的位置 \(x_i\) 和每分钟增加的不满值 \(b_i\),餐厅位于 \(X\)。配送员从餐厅出发,速度为 \(V^{-1}\)(每单位距离花费 \(V\) 分钟)。求使所有人最终不满值之和最小的配送方案。这是一个区间DP问题,关键在于如何高效计算配送过程中累积的不满值。

  2. 算法选择

    • 区间DP:定义 f[i][j][0/1] 为送完区间 \([i,j]\) 内所有人后,停在左端点(0)或右端点(1)的最小总不满值
    • 前缀和优化:使用前缀和数组 \(s[i]\) 快速计算未配送人员的不满值增长率之和
    • 双向扩展:从餐厅位置向左右两边扩展配送范围
  3. 关键步骤

    • 数据预处理
      • 将餐厅位置作为一个特殊点加入(\(b=0\)
      • 按位置排序所有点,找到餐厅位置 \(pos\)
      • 计算不满值增长率前缀和 \(s[i] = \sum_{k=1}^{i} b_k\)
    • 状态定义f[i][j][0] 表示送完 \([i,j]\) 后停在左端点 \(i\) 的最小总不满值;f[i][j][1] 表示停在右端点 \(j\) 的最小总不满值
    • 初始化f[pos][pos][0] = f[pos][pos][1] = 0(从餐厅出发)
    • 状态转移(从餐厅向两边扩展):
      • 停在左端点 \(i\)
        • \([i+1,j]\) 的左端点转移:f[i][j][0] = min(f[i][j][0], f[i+1][j][0] + cost)
        • \([i+1,j]\) 的右端点转移:f[i][j][0] = min(f[i][j][0], f[i+1][j][1] + cost)
      • 停在右端点 \(j\)
        • \([i,j-1]\) 的左端点转移:f[i][j][1] = min(f[i][j][1], f[i][j-1][0] + cost)
        • \([i,j-1]\) 的右端点转移:f[i][j][1] = min(f[i][j][1], f[i][j-1][1] + cost)
      • 代价计算cost = (未配送人员的不满值增长率之和) × (移动距离) × V
    • 结果min(f[1][n][0], f[1][n][1])
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N^2)\),双层循环枚举区间
    • 空间复杂度:\(O(N^2)\),三维DP数组
  5. 区间DP的核心思想

    • 区间定义f[i][j][k] 表示完成区间 \([i,j]\) 配送任务后的状态
    • 双向扩展:从初始位置(餐厅)向左右两边逐步扩展配送范围
    • 状态设计:增加一维表示当前停在左端点还是右端点,便于状态转移
    • 前缀和优化:快速计算未配送人员的不满值增长率之和,避免重复计算
    • 代价计算:每次移动新增的代价 = 未配送人员的不满值增长率之和 × 移动时间
    • 适用于区间覆盖、双向扩展、累积代价类问题

【解题思路】

img/Pasted image 20260612103928.png
img/Pasted image 20260612104536.png
img/Pasted image 20260612104940.png
img/Pasted image 20260612105337.png
img/Pasted image 20260612110156.png

【算法标签】

区间DP

【代码详解】

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

// 定义长整型别名,便于处理大数据
#define int long long

// 定义数组最大容量和无穷大常量
const int N = 1005, INF = 0x3f3f3f3f;

// 全局变量声明
int n;                  // 人数(含餐厅位置)
int s[N];               // 前缀和数组,s[i]表示前i个人的不满值增长率之和
int rx;                 // 餐厅位置坐标
int v;                  // 速度的倒数,即每单位距离花费的时间
int f[N][N][2];         // DP数组,f[i][j][0/1]表示送完[i,j]区间的人后停在左/右端点的最小总不满值

// 人的结构体
struct Node
{
    int x;              // 人的位置坐标
    int b;              // 该人每分钟增加的不满值(不满值增长率)
} p[N];

// 按位置坐标升序排序的比较函数
bool cmp(Node x, Node y)
{
    return x.x < y.x;
}

// 主函数入口(使用signed避免与long long冲突)
signed main()
{
    // 多组测试数据,直到文件结束
    while (~scanf("%d%d%d", &n, &v, &rx))
    {
        // 读取每个人的位置和不满值增长率
        for (int i = 1; i <= n; i++)
            cin >> p[i].x >> p[i].b;

        // 将餐厅位置作为一个特殊节点加入(不满值增长率为0)
        p[++n] = (Node){rx, 0};

        // 将所有节点按位置排序
        sort(p + 1, p + n + 1, cmp);

        // 找到餐厅在排序后数组中的位置
        int pos;
        for (int i = 1; i <= n; i++)
            if (rx == p[i].x)
            {
                pos = i;
                break;
            }

        // 计算不满值增长率的前缀和
        for (int i = 1; i <= n; i++)
            s[i] = s[i - 1] + p[i].b;

        // 初始化DP数组为无穷大
        memset(f, 0x3f, sizeof(f));

        // 起点(餐厅位置)处的总不满值为0
        f[pos][pos][0] = f[pos][pos][1] = 0;

        // 区间DP:从餐厅向两边扩展
        for (int i = pos; i >= 1; i--)          // 左端点从pos向左递减
            for (int j = pos; j <= n; j++)      // 右端点从pos向右递增
            {
                // 停在左端点的情况:从[i+1,j]区间转移过来

                // 从[i+1,j]的左端点走到i
                f[i][j][0] = min(f[i][j][0], f[i + 1][j][0] +
                    (s[i] + s[n] - s[j]) * (p[i + 1].x - p[i].x) * v);

                // 从[i+1,j]的右端点走到i
                f[i][j][0] = min(f[i][j][0], f[i + 1][j][1] +
                    (s[i] + s[n] - s[j]) * (p[j].x - p[i].x) * v);

                // 停在右端点的情况:从[i,j-1]区间转移过来

                // 从[i,j-1]的左端点走到j
                f[i][j][1] = min(f[i][j][1], f[i][j - 1][0] +
                    (s[i - 1] + s[n] - s[j - 1]) * (p[j].x - p[i].x) * v);

                // 从[i,j-1]的右端点走到j
                f[i][j][1] = min(f[i][j][1], f[i][j - 1][1] +
                    (s[i - 1] + s[n] - s[j - 1]) * (p[j].x - p[j - 1].x) * v);
            }

        // 输出最小总不满值(取停在左端点和右端点中的较小值)
        cout << min(f[1][n][0], f[1][n][1]) << endl;
    }

    return 0;
}

【运行结果】

5 1 0
1 1
2 2
3 3
4 4
5 5
55
posted @ 2026-06-17 17:25  团爸讲算法  阅读(6)  评论(0)    收藏  举报