题目描述
房间里放着 n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0)(0,0) 点处。
输入格式
第一行有一个整数,表示奶酪的数量 n。
第 2到第 (n+1) 行,每行两个实数,第(i+1) 行的实数分别表示第 i 块奶酪的横纵坐标xi,yi。
输出格式
输出一行一个实数,表示要跑的最少距离,保留 22位小数。
输入输出样例
输入 #1
4 1 1 1 -1 -1 1 -1 -1
输出 #1
7.41
说明/提示
数据规模与约定
对于全部的测试点,保证1≤n≤15,∣xi∣,∣yi∣≤200,小数点后最多有 3 位数字。
提示
对于两个点(x1,y1),(x2,y2),两点之间的距离公式为(x1−x2)^2+(y1−y2)^2。
2022.7.132022.7.13:新增加一组 HackHack 数据。
思路
状压dp
代码
/*用数组F[i][j]表示老鼠走到第i块奶酪,其他走过的奶酪坐标的二进制状态为j时的最短距离 (走过用1表示,没走过用0表示)
用循环来分别枚举所有可能的二进制状态、当前所在位置和在当前状态下可以到达的点
假设此时的二进制状态为s,起点为j,终点为i,用a[i][j]表示i到j之间的距离,F[j][k-(2^(i-1))]为当前位置在j 点且没有到达过i点的最短路径。最终得状态转移方程f[i][k] = min(f[i][k], f[j][k-(2^(i-1)] + a[i][j]);*/
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define min(a,b) (((a)<(b))?(a):(b)) //三目运算符,不过min函数是c++已经封装好的,因此这行可不用写
double a[20][20];从第i块到第j块的距离,使用两点之间距离公式(x1−x2)^2+(y1−y2)^2
double x[20],y[20];//每块奶酪的横、纵坐标
double F[18][34000];//状压DP数组 在第i个点上,走过的二进制状态的十进制表达为j时,最短的距离
int N;
double dis(int v,int w){//计算第v,w个奶酪之间的距离
return sqrt((x[v]-x[w]) * (x[v]-x[w]) + (y[v]-y[w]) * (y[v]-y[w]));//两点间距离公式 (x1−x2)^2+(跟上一行)(y1−y2)^2
}
int main(){
int i, j, k;
double ans;
memset(F, 127, sizeof(F));
ans = F[0][0];
scanf("%d", &N);
for(i = 1; i <= N; i++){
scanf("%lf%lf", &x[i], &y[i]);
}
x[0] = 0;
y[0] = 0;
for(i = 0; i <= N; i++){
for(j = i + 1; j <= N; j++){
a[i][j] = dis(i, j);//初始化
a[j][i] = a[i][j];
}
}
for(i = 1; i <= N; i++){//初始化
F[i][(1 << (i - 1))] = a[0][i];//在i点上且只有经过i点时距离是原点到i点的距离
}
for(k = 1; k < (1 << N); k++){//枚举所有二进制的状态
for(i = 1; i <= N; i++){
if((k & (1 << (i - 1))) == 0)
continue;//i的位置没被走过,所以跳过
for(j = 1; j <= N; j++){
if(i == j){
continue;//同一个点直接跳过
}
if((k & (1 << (j - 1))) == 0){
continue;//j的位置未走过
}
F[i][k] = min(F[i][k], F[j][k - (1 << (i - 1))] + a[i][j]);//递推式
}
}
}
for(i = 1; i <= N; i++){
ans = min(ans, F[i][(1 << N) - 1]);
}
printf("%.2f\n", ans);//保留2位小数
return 0;
}