先简化题意:n个坐标,起点(1,1),单次移动距离不能超过D,求从起点经过所有点再回到起点的最短距离。
首先用floyd得到d[i][j],表示i到j的最短路径,想清楚为什么要用floyd!!!,因为有每条边D的限制,相当于这条边超过D就不能用了,设为inf。例如D=4,i到j距离为5(超过D设为inf),i到k为3,k到j为3,则我们选择从i到k再到j,d[i][j]=3+3=6。
注意到n最大为20,每个点的经过情况可以用0/1状态表示,考虑状压DP。dp[i][j]表示i状态,最后一次是经过j点的最短距离。则前一次状态为i-(1<<j),枚举前一次状态最后一次经过的点(用lowbit巧妙得到1的位置),状态转移方程:dp[sta][i] = min(dp[sta][i], dp[lst_sta][x] + d[x][i]);最后用dp[sta][i]+d[i][0]更新答案,因为最后还要回到起点。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf = 2e18;
const int N = 25;
double d[N][N], dp[(1 << 20) + 9][N];
ll n, D, x[N], y[N];
double GetDist(int i, int j)
{
ll dx = x[i] - x[j];
ll dy = y[i] - y[j];
return sqrt(dx * dx + dy * dy);
}
int main()
{
cin >> n >> D;
for (int i = 0; i < n; i ++) cin >> x[i] >> y[i];
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
d[i][j] = GetDist(i, j) <= D ? GetDist(i, j) : inf;//每条边的长度有限制,不能超过D,超过就算无穷
//floyd
for (int k = 0; k < n; k ++)
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
if (i != j) d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
//初始化
for (int sta = 0; sta < (1 << n); sta ++)
for (int i = 0; i < n; i ++) dp[sta][i] = inf;
dp[1][0] = 0;
for (int sta = 2; sta < (1 << n); sta ++)
{
for (int i = 0; i < n; i ++)
{
if ((sta >> i & 1) == 0) continue;//非法状态
int lst_sta = sta ^ (1 << i);//上一状态
/*for (int j = 0; j < n; j ++)
{
if ((lst_sta >> j & 1) == 0) continue;
dp[sta][i] = min(dp[sta][i], dp[lst_sta][j] + d[j][i]);
}*/
int c = lst_sta;
while (c)//lowbit运算提取上一状态的每个1
{
int k = c & (-c);
c -= k;
int x = log10(k) / log10(2);
dp[sta][i] = min(dp[sta][i], dp[lst_sta][x] + d[x][i]);
}
}
}
double ans = inf;
for (int i = 0; i < n; i ++) ans = min(ans, dp[(1 << n) - 1][i] + d[i][0]);
cout << fixed << setprecision(2) << ans << '\n';
return 0;
}