题目描述
房间里放着 n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0) 点处。
输入格式
第一行有一个整数,表示奶酪的数量 n。
第 2到第 (n+1) 行,每行两个实数,第(i+1) 行的实数分别表示第 i 块奶酪的横纵坐标 xi,yi。
输出格式
输出一行一个实数,表示要跑的最少距离,保留 2 位小数。
解题思路
这个题目给我们的第一印象就是从所有结果中取最优解,那么涉及从所有结果中取最优解,我们不难想到使用搜索大法,直接深搜就完事了。接下来有几个难点要解决:
(1)如何表示奶酪坐标
题目给出的是所有奶酪的坐标,那么我们如果用矩阵表示的话其实很不方便,(最重要的是坐标似乎不一定是整数qwq),所以我们要另外想办法,既然它给出题目是给定了n块奶酪,那么我们就尝试用数组来存储,我们可以分别用一个x数组和y数组来分别存储每块奶酪的横坐标和纵坐标,那么下标肯定就是当前是哪一块奶酪啦。
(2)怎样去遍历每一种结果
同八皇后问题,这个题其实也不难看出是一个求全排列的问题,比如一共三块奶酪【1, 2, 3】,那么我们所有的办法就是【1, 2, 3| 1, 3, 2| 2, 1, 3| 2, 3, 1| 3, 1, 2 | 3, 2, 1】然后从这几种方法中去找最优的解即可,那么我们是不是可以用和八皇后一样的方法来进行状态压缩呢?我们从题目中可以知道最多也不过是15个奶酪,那最多只需要16位就可以表示所有的状态了,所以用一个整形表示是可以把所有状态都表示的,至于如何表示,这里不再赘述,我们直接去看我之前的八皇后问题,那里讲的很详细https://blog.csdn.net/m0_74246669/article/details/136822377?spm=1001.2014.3001.5501
具体实现方法就看代码吧,里面也有比较详细的注释。
题解代码
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
#define MASK(n) ((1 << ((n) + 1)) - 2) //生成掩码->例如“01111111”,1表示可用,0表示不可用
#define MUL(n) ((n) * (n)) //一个n * n的宏
//MAXN是最大的奶酪数量,MAXT是最大的掩码状态数量,INF是答案ans的初始值(极大值)
const int MAXN = 20, MAXT = 7e5, INF = 1e9;
//存储每个奶酪(包括小老鼠起始位置)的下标
double x[MAXN], y[MAXN];
//dis数组表示每两个点(本题用下标表示每个点)之间的距离
//dp[t][i]数组用来表示达到t状态时,第i个奶酪的最短距离
double dis[MAXN][MAXN], dp[MAXT][MAXN] = {0};
//用来表示每个状态及其权值的映射
int ind[MAXT];
int n;
double ans = INF;
//获取距离函数
double getDis(int i, int j) {
return sqrt(MUL(x[i] - x[j]) + MUL(y[i] - y[j]));
}
//开始深搜
void dfs(int t, int curr, double s) {
//状态为零的时候表示全部枚举完了
if (t == 0) {
//ans取最小值
if (ans > s) ans = s;
return ;
}
//递归枚举所有的状态
for (int k = t; k; k -= (-k & k)) {
//to表示将要到达的奶酪下标
//next_t表示下一层的状态
int to = ind[-k & k], next_t = (t ^ (1 << to));
//做一个剪枝优化:如果当前位置走的路程不比dp数组记录的小,那么没有必要继续递归
if (dp[next_t][to] != 0 && dp[next_t][to] <= s + dis[curr][to]) continue;
//记录到达to奶酪next_t状态所走的最小路程
dp[next_t][to] = s + dis[curr][to];
//同上,如果当前路程以及大于等于当前最优路程,那也没必要继续搜索下去
if (ans <= s + dis[curr][to]) continue;
dfs(next_t, to, s + dis[curr][to]);
}
return ;
}
int main() {
cin >> n;
x[0] = y[0] = 0;
for (int i = 1; i <= n; i++) scanf("%lf%lf", x + i, y + i);
for (int i = 0; i <= n ;i++) {
for (int j = i; j <= n; j++) {
dis[i][j] = dis[j][i] = getDis(i, j);
}
}
for (int i = 0; i <= n + 1; i++) ind[1 << i] = i;
dfs(MASK(n), 0, 0);
printf("%.2lf\n", ans);
return 0;
}
总结
这个题目是要遍历所有的结果,所以我们基于全排列去做深度优先搜索,而其中用到的一个很神奇的技巧就是状态压缩,即把一个数组压缩成一个整型,这个技巧个人觉得很值得学习和练习。