题目:
在一个二维平面上,散落着n个物品,现在要将所有物品收集到起点(x0,y0),一只手可以拿一个物品,所以最多拿两个物品。从一个点到另一个点花费的时间为
(x1 - x2)^2 + (y1 - y2) ^ 2
,每次要从起点出发,然后拿一个物品或者两个物品后要返回起点。
现在需要你确定收集物品的顺序,使得总的物品所需要的时间最少。
1≤ n ≤ 24, 0 ≤ x, y ≤100
解决:
状压dp
状态转移时,选择一个物品或两个物品,然后转移。
dp[(k | (1 << i) | (1 << j))] = dp[k] + dist[0][i + 1] + dist[i + 1][j + 1] + dist[j + 1][0]
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 35, INF = 0x3f3f3f3f;
struct Node{
int x, y;
}a[N];
int dist[N][N];
int n; // 第0个坐标表示起点
int get_dist(Node a, Node b)
{
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
int dp[(1<<24)], pre[(1 << 24)];
int main()
{
cin >> a[0].x >> a[0].y >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i].x >> a[i].y;
// 计算点的距离
for (int i = 0; i <= n; i ++ )
for (int j = 0; j <= n; j ++ )
dist[i][j] = get_dist(a[i], a[j]);
memset(dp, 0x3f, sizeof dp);
dp[0] = 0;
for (int k = 0; k < (1 << n); k ++ )
{
// 我们是从当前状态k转移到状态(k | (1 << i) || (1 << j))
if (dp[k] == INF) continue;
for (int i = 0; i < n; i ++ )
{
if ((k >> i) & 1) continue; //
for (int j = i; j < n; j ++ )
{
if ((k >> j) & 1) continue;
int &x = dp[(k | (1 << i) | (1 << j))];
// 0是起点,每次从0走到i+1,然后从i+1走到j+1,然后再走到0
int y = dp[k] + dist[0][i + 1] + dist[i + 1][j + 1] + dist[j + 1][0];
// printf("k = %d, i = %d , j = %d, end = %d, start = %d\n", k, i + 1, j + 1, x, y);
if ( x > y)
{
x = y;
pre[(k | (1 << i) | (1 << j))] = k;
}
}
// 每次找到两种转移方案,(1.走到i+1就回去,2.走到i+1再走到j+1)
// 因为不过不break,答案的区别仅仅是每个方案的先后顺序不同,所以break可以避免重复。
break;
}
}
// cout << (1 << n) << endl;
// cout << (1 << n - 1) << endl;
// cout << (1 << n) - 1 << endl;
cout << dp[(1 << n) - 1] << endl;
int ed = (1 << n) - 1;
// cout << ed << endl;
while (ed)
{
printf("0 ");
int st = pre[ed];
// printf("ed = %d, st = %d\n", ed, st);
for (int i = 0; i < n; i ++ )
if (!(st >> i & 1) && (ed >> i & 1))
printf("%d ", i + 1);
ed = st;
}
puts("0");
return 0;
}