题目
- 题目描述
维修工要给n个客户更换设备,为每个用户更换一个设备。维修工背包内最多装k个设备,如果背包里有设备可以直接前往下一个客户更换或回公司补充设备,没有则需要回公司取设备。这些客户有优先级,维修工需要 按照优先级给客户更换设备,优先级level用数字表示,数字小的优先级高。维修工从公司出发,给n个客户更换设备,最后再返回公司,请计算维修工完成这项工作所需要经历的最短总距离是多少,维修工可以走斜线。 - 解答要求
时间限制: C/C++ 1000ms, 其他语言:2000ms
内存限制: C/C++256MB, 其他语言:512MB - 输入
第一行两个正整数 n,k (1≤k≤n≤2000),表示客户数和维修工背包容量。
第二行两个正整数 x,y,用空格分隔(1 ≤ x ,y ≤ 1 0 6 10^6 106),表示公司的坐标
接下来n行每行三个正整数 xi,yi,leveli,用空格分隔 ( 1 ≤ x i , y i ≤ 1 0 6 , 1 ≤ l e v e l i < = n 1≤x_i,y_i≤10^6,1≤level_i<=n 1≤xi,yi≤106,1≤leveli<=n)。 ( x i , y i ) (x_i,y_i) (xi,yi) 表示第i个客户的位置坐标, l e v e l i level_i leveli 表示第i个客户的优先级,保证所有客户优先级不同,客户和公司坐标不会重叠。 - 输出
输出最短总距离,结果四舍五入并保留一位小数,例如: 9.0 - 样例1
输入
3 2
1 1
3 1 1
1 2 2
3 2 3
输出
9.2 - 样例2
输入
4 1
2 2
1 1 1
1 3 4
3 1 2
3 3 3
输出
11.3
解题思路
- 创建 vector<struct> 存储客户信息,其中每个元素是1个结构体存有客户的坐标和优先级
首先构造 customer 客户结构体用于存放客户的坐标以及优先级信息,main 函数内构造 vector 数组,其中每一个元素都是1个customer 结构体用户。 - 重载小于运算符将客户按照优先级排序
在 customer 客户结构体内重载小于运算符,比较客户的优先级;main函数内调用 sort 函数对 客户的优先级进行排序。 - 创建二维 vector 表示完成第i个客户且剩余j个设备后,从该客户到完成剩余客户最终返回公司所需的最短距离
构建 二维 vector 数组 dp 并将元素初始化为0,dp[i][j] 表示的是完成第i(i=0,1,2…n-1)个客户,并且背包里还有j个设备时,从该客户到完成剩余所有客户的维修最终返回公司的最短距离。 - 创建 dfs 递归函数用于更新 dp
- 首先检查dp[i][j]是否已经被计算过:如果dp[i][j]大于0,意味着这个状态之前已经处理过了,直接返回存储的结果以避免重复计算。
- 初始化 dp[i][j] 为 double 的最大值表示距离无穷大,标记正在处理(dp[i][j]>0)但刚开始(dp[i][j]无穷大)。
- 若当前客户不是最后一个客户:
- 直接返回公司取设备 :结果1 = 从当前客户到公司的距离+ 从公司到第i+1客户距离 + 从第i+1客户到处理完所有客户最终返回公司所需的最短距离(递归调用本函数实现);
- 若当前包内有设备则到下一个客户 :结果2 = 从当前第i客户到第i+1客户的距离+从第i+1客户到处理完所有客户最终返回公司所需的最短距离(递归调用本函数实现);
- 判断结果1和结果2(若有)的最小值作为 dp[i][j]。
- PS:上述没有判断包内无设备的情况,主要是考虑了以下情况:
处理完客户p还有设备, 【从客户p到客户p+1】➕【从客户p+1到公司】➕【从公司到客户p+2 】> 【从客户p到公司】➕【从公司到客户p+1】➕【从客户p+1到客户p+2】
- 若当前客户i是最后1个客户即n-1,则直接返回从该客户到公司的距离 。
- main 函数内调用 dfs 函数计算 最终结果:从公司到第1个客户的距离+从第1个客户到处理完所有客户再返回公司的最小距离 。
代码
BUG 代码
- 该代码选取下个客户时,未考虑客户优先级,直接寻找的是距离当前地直接距离最近的客户即也没有考虑最短路径规划。对于某些案例不通过。
#include <iostream>
#include <vector>
#include <cmath>
#include <iomanip>
#include <algorithm>
using namespace std;
class Customer {
public:
int x, y, level;
bool visited;
Customer(int x, int y, int level) : x(x), y(y), level(level), visited(false) {}
// 其他用户到该用户的距离
double distanceTo(const Customer& other) const {
return sqrt(pow(x - other.x, 2) + pow(y - other.y, 2));
}
// <运算符重载
bool operator<(const Customer& other) const {
return level < other.level;
}
};
// 包内设备已用完 且 全部客户还未处理完,则需要返回公司拿设备
bool shouldReturnToCompany(const vector<Customer>& customers, int k) {
int visitedCount = 0;
for (const auto& customer : customers) {
if (customer.visited)
visitedCount++;
}
return visitedCount % k == 0 && visitedCount < customers.size();
}
// 返回还未处理的客户数量
int customersRemaining(const vector<Customer>& customers) {
int remaining = 0;
for (const auto& customer : customers)
if (!customer.visited)
remaining++;
return remaining;
}
int main() {
int n, k;
cin >> n >> k; // n个用户,k个容量
int companyX, companyY;
cin >> companyX >> companyY;
vector<Customer> customers;
for (int i = 0; i < n; ++i) {
int x, y, level;
cin >> x >> y >> level;
customers.emplace_back(x, y, level);
}
//按优先级排序客户
sort(customers.begin(), customers.end());
double totalDistance = 0.0;// 初始化总距离为0
Customer currentLocation(companyX, companyY, 0);// 初始化当前地为公司
while (true) {
Customer* nextCustomer = nullptr;
// 获取下一个要处理的客户(距当前地最近的客户,未考虑优先级)
double minDistance = numeric_limits<double>::max();// 底层是double所代表的最大值
for (auto& customer : customers){
if (!customer.visited) { // 若当前客户还未处理
double distance = customer.distanceTo(currentLocation); // 获取当前地到未处理客户的距离
if (distance < minDistance) { // 到该客户的距离<最短距离
minDistance = distance;
nextCustomer = &customer;
}
}
}
//若所有客户都已处理 或者 当前地到客户的距离太大,则跳出循环直接输出
if (nextCustomer == nullptr) {
break;
}
totalDistance += minDistance;
nextCustomer->visited = true; // 标记当前客户已处理
currentLocation = *nextCustomer;// 当前地址更新
//若所有客户都已访问,则跳出循环直接输出
if (customersRemaining(customers) == 0) {
break;
}
// 若需要返回公司拿设备,则加上从当前地址返回公司的距离
if (shouldReturnToCompany(customers, k)) {
totalDistance += currentLocation.distanceTo(Customer(companyX, companyY, 0));
currentLocation = Customer(companyX, companyY, 0); // 将当前地更新为公司
}
}
totalDistance += currentLocation.distanceTo(Customer(companyX, companyY, 0)); //都处理完后,最后 返回公司
cout << fixed << setprecision(1) << totalDistance << endl;
return 0;
}
最终代码
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <vector>
#include <cmath>
using namespace std;
struct customer
{
int x, y, level;
bool operator < (const customer& other) const {
return level < other.level;
}
};
// 计算距离
double cal_dis(int x1, int y1, int x2, int y2) {
return (double)sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
}
/*
记忆化搜索,dp[i][j]表示维修完第i个客户(i=0,1,2..n-1),并且背包中还剩下j个设备时,完成剩余所有客户再返回公司的距离。
dfs(i, j)用于更新 dp[i][j]。
*/
double dfs(int i, int j,const int &n ,const int& k,const vector<customer> & customers,vector <vector<double>> & dp ,const int &cx,const int &cy) {
if (dp[i][j] > 0) //首先检查dp[i][j]是否已经被计算过。如果dp[i][j]大于0,意味着这个状态之前已经处理过了,直接返回存储的结果以避免重复计算。
return dp[i][j];
double dis = cal_dis(customers[i].x, customers[i].y, cx, cy); // 计算从当前客户i的位置到公司位置的距离。
double ans = numeric_limits<double>::max();
if (i < n - 1) { // 如果当前客户不是最后一个
ans = min(ans, dfs(i + 1, k - 1,n,k, customers, dp, cx, cy) + dis + cal_dis(cx, cy, customers[i + 1].x, customers[i + 1].y)); // 返回公司
if (j > 0) { // 若包内还有设备
ans = min(ans, dfs(i + 1, j - 1, n, k, customers, dp, cx, cy) + cal_dis(customers[i].x, customers[i].y, customers[i + 1].x, customers[i + 1].y));
}
}
else // 如果是最后一家即:i=n-1,则修完直接回公司
ans = dis;
return dp[i][j] = ans;
}
int main() {
int n, k,companx,company;
cin >> n >> k;
cin >> companx >> company;
vector<customer> customers(n);
for (int i = 0; i < n; i++)
cin >> customers[i].x >> customers[i].y >> customers[i].level;
sort(customers.begin(), customers.end());
vector <vector<double>> dp(n, vector<double>(n, 0));
double result = cal_dis(customers[0].x, customers[0].y, companx, company) + dfs(0, k - 1, n, k, customers, dp, companx, company);// 输出结果
printf("%.1f",result);
return 0;
}