题意:平面上有n个点,一开始在原点,每次从原点出发最多经过2个点就必须返回原点,问走完所有点的最小距离,和一种走法。\((n \leq 24)\)
考虑贪心,由于每个点到达原点的路都至少走一次,所以可以把这些边先加进去然后去掉,然后变成在n个点的完全图中选边使其成为二分图且总权值和最小,这时贪心显然没有正确性。
根据数据范围考虑状压dp,暴力转移复杂度不能接受,容易发现对于任意两次从原点出发,取点,返回原点的过程,将它们互换不会影响结果。从而只需要记录哪些点走了哪些点没走。开一个\(2^{24}\)大小的数组即可,另外为了存储路径,还要再开一个相同大小的数组,这样已经几乎卡满空间。
考虑如何保证不反复尝试同一走法的不同顺序,只需要在找到第一个可以转移的单次的两个取点的第一个点即可,这样保证了不会反复转移。
注意输出路径,开一个数组存放一种dp状态是又哪一种转移过来的。(为什么不能反过来存,感觉也有正确性,毕竟是最优子结构)。
接下来是简单转移,枚举i,j:
\[dp_{t|(1<<i-1|1<<j-1)}=dp_t+dis[0][i]+dis[0][j]+dis[i][j] \]#include <iostream>
#include <cstdio>
#include <algorithm>
#include <assert.h>
using namespace std;
const int N = 25;
const long long INF = 0x7ffffffff;
struct point
{
long long x, y;
} p[N];
long long getdis(int a, int b)
{
int x1 = p[a].x, x2 = p[b].x, y1 = p[a].y, y2 = p[b].y;
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
}
int n;
int dis[N][N];
long long dp[(1 << N - 1) + 5], route[(1 << N - 1) + 5];
int main()
{
ios::sync_with_stdio(false);
cin >> p[0].x >> p[0].y;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> p[i].x >> p[i].y;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= n; j++)
dis[i][j] = getdis(i, j);
for (int i = 1; i <= (1 << n) - 1; i++)
dp[i] = INF;
dp[0] = 0;
for (int t = 0; t <= (1 << n) - 1; t++)
{
if (dp[t] == INF)
continue;
for (int i = 1; i <= n; i++)
{
if (t & (1 << i - 1))
continue;
for (int j = 1; j <= n; j++)
{
if (t & (1 << j - 1))
continue;
if (dp[t] + dis[0][i] + dis[0][j] + dis[i][j] < dp[t | (1 << i - 1) | (1 << j - 1)])
{
dp[t | (1 << i - 1) | (1 << j - 1)] = dp[t] + dis[0][i] + dis[0][j] + dis[i][j];
route[t | (1 << i - 1) | (1 << j - 1)] = t;
}
}
break;
}
}
cout << dp[(1 << n) - 1] << endl;
for (int i = (1 << n) - 1; i; i = route[i])
{
cout << 0 << " ";
int temp = route[i] ^ i;
for (int j = 1; j <= n; j++)
if (temp & (1 << j - 1))
cout << j << " ";
}
cout << 0 << endl;
}