原题链接https://codeforces.com/problemset/problem/8/C
这题自己写状压是没问题,但是细节没把握好,wa了,然后参考了大佬的博客.
据说这题还可以暴搜实现,懒得想了(说不定剪枝啥的不好想)。
题意:
一个女孩整理箱子,箱子的位置不可以改变,告诉你箱子和每个行李的位置,她一次可以拿两个或者是一个行李,女孩移动时间花费是她移动距离的平方,求最小花费时间和移动方案。
思路: 以下思路来自https://www.luogu.com.cn/blog/hxy1117/solution-cf8c
拿到题, \(n<=24n<=24\) 的数据范围和512MB的空间限制基本上就标志着这道题是一个标准的状压。为了节省空间,我们就 \(1<<i-1\) 表示第 \(i\) 个物品有无被取过。由于一次可以选择拿一个或者两个物品,考虑在状态转移的时候枚举这次拿的两个物品(如果两个物品相同就处理为这次只拿一个)。由于要输出拿的方法,我们就再用一个数组记录当前状态是从之前的哪个状态转移过来的即可。
需要注意的是,可以证明,若设每次离开手提包捡拾物品为一次,则各次之间的顺序不影响最终答案。如,第一次拿物品1,第二次拿物品2,3与第一次拿物品2,3,第二次拿物品1的所用时间是一样的。这样,我们在转移的时候就从编号小的物品向编号大的物品找寻,只要找到了当前的一种可用方案就可以进入下一个状态了,这样可以节省时间(同时避免重复计算)。
代码如下
int a, b, n;
struct point{
int x, y;
}p[N];
struct node{
int x, y, z;
};
int dist[N][N];
int f[1 << 24];
node pre[1 << 24];
int get(int p1, int p2)
{
int x = p[p1].x - p[p2].x;
int y = p[p1].y - p[p2].y;
return x * x + y * y;
}
int main()
{
IOS;
cin >> a >> b >> n;
p[0].x = a, p[0].y = b;
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 ++)
dist[i][j] = get(i, j);
memset(f, 0x3f, sizeof f);
f[0] = 0;
for(int k = 0 ; k < (1 << n) ; k ++)
{
for(int i = 0 ; i < n ; i ++) //物品按照编号递增枚举
{
if((k >> i) & 1) continue; //如果当前物品 1 已经被拿过了
for(int j = 0 ; j < n ; j ++)
{
if((k >> j) & 1) continue; //如果当前物品 2 已经被拿过了
if(f[k | (1 << i) | (1 << j)] > f[k] + dist[0][i + 1] + dist[i + 1][j + 1] + dist[j + 1][0])
{
f[k | (1 << i) | (1 << j)] = f[k] + dist[0][i + 1] + dist[i + 1][j + 1] + dist[j + 1][0];
pre[k | (1 << i) | (1 << j)] = {k, i + 1, j + 1};
}
}
break; //由于答案和顺序无关
// 因此我们只要能把物品 1 放进去,就可break
}
}
cout << f[(1 << n) - 1] << endl;
vector<int> ans;
auto t = pre[(1 << n) - 1];
while(1)
{
ans.push_back(0);
ans.push_back(t.y);
if(t.y != t.z)
ans.push_back(t.z);
if(t.x == 0) break;
t = pre[t.x];
}
ans.push_back(0);
for(int i = ans.size() - 1 ; i >= 0 ; i --) cout << ans[i] << " ";
cout << endl;
return 0;
}