吃奶酪
题目描述
房间里放着 n n n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 ( 0 , 0 ) (0,0) (0,0) 点处。
输入格式
第一行有一个整数,表示奶酪的数量 n n n。
第 2 2 2 到第 ( n + 1 ) (n + 1) (n+1) 行,每行两个实数,第 ( i + 1 ) (i + 1) (i+1) 行的实数分别表示第 i i i 块奶酪的横纵坐标 x i , y i x_i, y_i xi,yi。
输出格式
输出一行一个实数,表示要跑的最少距离,保留 2 2 2 位小数。
样例 #1
样例输入 #1
4
1 1
1 -1
-1 1
-1 -1
样例输出 #1
7.41
提示
数据规模与约定
对于全部的测试点,保证 1 ≤ n ≤ 15 1\leq n\leq 15 1≤n≤15, ∣ x i ∣ , ∣ y i ∣ ≤ 200 |x_i|, |y_i| \leq 200 ∣xi∣,∣yi∣≤200,小数点后最多有 3 3 3 位数字。
提示
对于两个点 ( x 1 , y 1 ) (x_1,y_1) (x1,y1), ( x 2 , y 2 ) (x_2, y_2) (x2,y2),两点之间的距离公式为 ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 \sqrt{(x_1-x_2)^2+(y_1-y_2)^2} (x1−x2)2+(y1−y2)2。
2022.7.13 2022.7.13 2022.7.13:新增加一组 Hack \text{Hack} Hack 数据。
分析
- 此题的思路不难,但是想做对挺不容易的,一看n的大小15,就可以想到用全排列,暴力逐个判断走完每个点不同排列方式,找最短距离,但是如果仅仅一个简单的最优性剪枝if (dis >= ans) return;这是不够的,会有一个点TLE;需要用到状态压缩剪枝;
- 为什么需要状态压缩? 由于到达某个点的状态有多种情况,比如到达12,可能之前走过1 2 3,也可能走过4 5 6,所以记录状态时,可以用状态压缩,用二进制序列的十进制数来表示每个状态,用一个数组f[][],一维就是点的编号,二维就是到达该点是的状态(已走过的点组成二进制序列的十进制数);
- f数组存了到达不同点所经过的状态,以及他的当前最优值,如果下次换一个状态还到达该点,发现新的状态所用距离不如上一次,就直接continue(不是return,不能顺手直接return,刚开始return卡了我一天找不到错误,可能还是不够理解),当前 if (f[i][newStatus] != 0 && f[i][newStatus] <= f[lastid][status] + calDis(last, {xx, yy}))不满足,只能说明第i个点新的状态已比较优,不用再通过上一个状态连当前的点,可以去判断下一个点,也就是i++,就不能直接return走;
- 由于从起点0,0出发,dfs的参数就不能仅仅用lastid去获取上个点的信息(还需要加一个last存上一个点的坐标),比如(0,0)出发,搜第一个点,然后利用i=0,和lastid=0,同时查点,但起始点(0,0)并不是坐标编号为0,就会错误计算;还需要注意坐标为实数,说明还x,y可能为小数,所以double存,不然下面第二轮测试点过不去,WA掉;
#include <bits/stdc++.h>
using namespace std;
int n;
double x, y;
double ans = 10000000;
vector<pair<double, double>> v;
double vis[20];
//状态记录 d[12][42501] 表示走到编号为12的点,所经过的点的编号为42501二进制序列为1的数
double f[20][33000];
//两个点的距离
double calDis(pair<double, double> p1, pair<double, double> p2) {
return sqrt((p1.first - p2.first) * (p1.first - p2.first) +
(p1.second - p2.second) * (p1.second - p2.second));
}
//lastid:上一个坐标的编号,last:上一个点的坐标,status为当前走过的点编号组成的二进制序列的十进制值
void dfs(int u, int lastid, pair<double, double> last, int status, double dis) {
//剪枝
if (dis >= ans)
return;
if (u == n) {
ans = min(ans, dis);
return;
}
for (int i = 0; i < n; i++) {
double xx = v[i].first, yy = v[i].second;
if (!vis[i]) {
//更新状态,因为加上了i这个点,status + 2^i(1左移i位就是2的i的值)
int newStatus = status + (1 << i);
//状态压缩剪枝
if (f[i][newStatus] != 0 && f[i][newStatus] <= f[lastid][status] + calDis(last, {xx, yy}))
continue;
f[i][newStatus] = f[lastid][status] + calDis(last, {xx, yy});
vis[i] = 1;
dfs(u + 1, i, {xx, yy}, newStatus, dis + calDis(last, {xx, yy}));
vis[i] = 0;
}
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> x >> y;
v.push_back({x, y});
}
dfs(0, 0, {0, 0}, 0, 0);
printf("%.2lf", ans);
return 0;
}