【题解】P8733 题解
P8733 题解
看到这种类似“旅行商问题”的题目时,我们可以考虑使用搜索,但是搜索很慢,所以考虑使用状压动态规划。
众所周知,状压有两种求解方法,一种是“人人为我”(用别人去更新自己),一种是“我为人人”(用自己去更新别人)。这里以“我为人人”为例。
设当前状态为 ,则 为当前状态为 ,且最后一步选了 的最短距离。那么“我为人人”就要考虑往当前状态上加一个结点。
不妨设为加的这个点为 ,首先 不能在 中(显然你不能走重复的结点),然后我们让 S 加入点 ,不妨设加入后状态为 ,那么有如下转移方程:
其中 表示 之间的“距离”,根据题意,我们可以做如下操作来计算这个“距离”:
-
如果两点间的欧几里得距离为 ,且 ,那么显然这两个点之间可以通行连边,并设置权值为 。
-
否则,两个点不能通,设置权值为 。
然后跑 Floyd 即可,跑完后的数组就是 。
注意 要在 中,可以枚举 到 然后逐一判断。
答案为 ,其中 , 为 到 都选的状态。
最后给出本题要用到的三个二进制的知识点:
-
S | (1 << (x - 1))表示给状态 加入元素 。 -
S & (1 << (x - 1))表示判断 是否在 中。如果在,值为 ,否则为 。 -
(1 << n) - 1表示 到 都选的状态。
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 21;
const int M = 1048576 + 5;
int n, d;
int x[N], y[N]; // 点的坐标
double dp[N][N]; // dis 数组
double f[M][N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> d;
for(int i = 1;i <= n;i++)
{
cin >> x[i] >> y[i];
}
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
double dis = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j])); //计算欧几里得距离
//根据上述方式“建图”
if(dis <= d) dp[i][j] = dis;
else dp[i][j] = 1e7;
}
}
//跑 Floyd
for(int k = 1;k <= n;k++)
{
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
}
}
}
fill(*f, *f + M * N, 1e7);
f[1][1] = 0;
for(int S = 0;S < (1 << n);S++)
{
for(int i = 1;i <= n;i++)
{
if(S & (1 << (i - 1))) // i 得在 S 中
{
for(int j = 1;j <= n;j++)
{
if(S & (1 << (j - 1))) continue; //j 如果在 S 中,舍
f[(S | (1 << (j - 1)))][j] = min(f[(S | (1 << (j - 1)))][j], f[S][i] + dp[i][j]); // 更新
}
}
}
}
double ans = 0x7fffffff;
for(int i = 1;i <= n;i++)
{
ans = min(ans, f[(1 << n) - 1][i] + dp[i][1]); //计算答案
}
cout << fixed << setprecision(2) << ans << endl;
return 0;
}

浙公网安备 33010602011771号