题解: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
【核心思想】
-
问题分析:给定 \(N\) 个人在数轴上的位置 \(x_i\) 和每分钟增加的不满值 \(b_i\),餐厅位于 \(X\)。配送员从餐厅出发,速度为 \(V^{-1}\)(每单位距离花费 \(V\) 分钟)。求使所有人最终不满值之和最小的配送方案。这是一个区间DP问题,关键在于如何高效计算配送过程中累积的不满值。
-
算法选择:
- 区间DP:定义
f[i][j][0/1]为送完区间 \([i,j]\) 内所有人后,停在左端点(0)或右端点(1)的最小总不满值 - 前缀和优化:使用前缀和数组 \(s[i]\) 快速计算未配送人员的不满值增长率之和
- 双向扩展:从餐厅位置向左右两边扩展配送范围
- 区间DP:定义
-
关键步骤:
- 数据预处理:
- 将餐厅位置作为一个特殊点加入(\(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)
- 从 \([i+1,j]\) 的左端点转移:
- 停在右端点 \(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)
- 从 \([i,j-1]\) 的左端点转移:
- 代价计算:
cost = (未配送人员的不满值增长率之和) × (移动距离) × V
- 停在左端点 \(i\):
- 结果:
min(f[1][n][0], f[1][n][1])
- 数据预处理:
-
时间/空间复杂度:
- 时间复杂度:\(O(N^2)\),双层循环枚举区间
- 空间复杂度:\(O(N^2)\),三维DP数组
-
区间DP的核心思想:
- 区间定义:
f[i][j][k]表示完成区间 \([i,j]\) 配送任务后的状态 - 双向扩展:从初始位置(餐厅)向左右两边逐步扩展配送范围
- 状态设计:增加一维表示当前停在左端点还是右端点,便于状态转移
- 前缀和优化:快速计算未配送人员的不满值增长率之和,避免重复计算
- 代价计算:每次移动新增的代价 = 未配送人员的不满值增长率之和 × 移动时间
- 适用于区间覆盖、双向扩展、累积代价类问题
- 区间定义:
【解题思路】





【算法标签】
区间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
浙公网安备 33010602011771号