题解 CF8C Looking for Order
思路分析
一看数据 \(n\le 24\),啊很快啊,啪的一下,我们就会想到, \(2^{24}=16,777,216\),对于 dfs 暴力来说有点大,但是我们可以选择用状压。诶状压,把它记在脑子里。
接下来考虑题目的具体要求:
从起点出发,取走至多两个物品,再回到起点,重复此过程直到物品被取完。
我们令起点为 \(0\),用 \(dis_{i,j}\) 表示物品 \(i\) 到物品 \(j\) 之间的距离,则有:
- 若此次只取物品 \(a\),则对总路程的贡献为 \(dis_{0,a}+dis_{a,0}\);
- 若此次同时取走物品 \(a,b\),则对总路程的贡献为 \(dis_{0,a}+dis_{a,b}+dis_{b,0}\)。
看到这里,相信聪明的你一定可以发现:
无论方案如何,每个物品到起点之间的路径都至少被经过一次。
因此,对于每一件物品,我们不妨先无视其必然会经过的通向起点的路径,只考虑另一条路径。我们会发现,对于每一个物品,它的第二条路径只有两种可能:
- 还是通往起点;
- 通向另一个物品。
考虑到 \(n\le 24\) 的良心数据范围,我们大可枚举每一个物品究竟是要连向起点,还是连向其他物品。考虑到文章开头状压的思想,若我们用一个01串表示每一个物品是取还是不取,我们便可以很容易地由一个状态得出另一个状态。那么你们想到了什么方法——状压 DP!
但是啊,正经人谁要写 DP 啊,因此这题我们还可以用记忆化搜索。
方法解析
显然,我们应当预处理出 \(dis\) 数组,并且求出 \(sum\) 表示每一个物品到起点的距离之和。
接下来考虑 dfs
我们在函数 \(dfs\) 中定义变量 \(u,s\) 分别表示当前的状态。
考虑到本题一个重要的性质:路径长度与取物顺序无关。具体的说,我究极是先取 \(1,2\) 再取 \(3,4\) 还是先取 \(3,4\) 还是 \(1,2\) 和答案是没有关系的。因此,我们不应该每次枚举要在哪两个物品之间增加一条路径,而是应该顺序枚举每一个物品要连向哪里。
具体地,我们在 \(dfs\) 中应当再定义一个变量 \(x\),表示当前考虑到哪一个物品。那么在 \(dfs\) 中我们仅需要 \(O(n)\) 的时间即可完成状态转移。
具体实现请见代码。
接着考虑方案输出。
我们定义 \(now_i\) 表示物品 \(i\) 的路径连向何处。例如,若我们在物品 \(a,b\) 之间选择一条道路,那么则令 \(now_a=b,now_b=a\);若我们在物品 \(a\) 与起点间选择一条道路,那么则令 \(now_a=0\),在每次更新最优答案后将 \(now\) 赋值到 \(ans\) 中。
在输出时,若 \(ans_i=0\),则输出 \(i\),否则,我们输出 \(i\) 与 \(ans_i\)。并且,由于每一条路径的两个端点都能通过 \(ans\) 表示这条路径的另一个端点,我们可以输出 \(ans_i>i\) 的路径以防止重复。然后在每组输出直接用 \(0\) 间隔即可。
注意点
- 不要忘了 \(dfs\) 里的 \(x\);
- 输出路径记得判重;
- 别忘了最后加上 \(sum\)。
AC 代码
#include<iostream>
#include<cstring>
#include<map>
using namespace std;
const int N = 25;
struct Node
{
int x, y;
}a[N];
map<int, int>M;//用map存储此状态的最小距离
int now[N], ans[N];
int dis[N][N], n, way;
void dfs(int x, int u, int s)
{
int d = M[u];//更新最小值
if(d != 0 && d < s) return;
M[u] = s;
if(x > n)
{
memcpy(ans, now, sizeof(now));
return;
}
if(u & (1 << (x - 1)))//若该物品已被取走,直接考虑下一个物品
{
dfs(x + 1, u, s);
return;
}
now[x] = 0;
dfs(x + 1, u | (1 << (x - 1)), s + dis[0][x]);//连向起点
for(int i = x + 1; i <= n; i ++)//连向其他物品
{
if(u & (1 << (i - 1))) continue;
now[x] = i, now[i] = x;
dfs(x + 1, u | (1 << (x - 1) | (1 << (i - 1))), s + dis[x][i]);
}
}
int main()
{
int bx, by, sum = 0;
cin >> bx >> by >> n;
for(int i = 1; i <= n; i ++)
{
cin >> a[i].x >> a[i].y;
for(int p = 1; p < i; p ++)//预处理dis
{
dis[i][p] = dis[p][i] = (a[i].x - a[p].x) * (a[i].x - a[p].x) + (a[i].y - a[p].y) * (a[i].y - a[p].y);
}
dis[0][i] = (a[i].x - bx) * (a[i].x - bx) + (a[i].y - by) * (a[i].y - by);
sum += dis[0][i];//别忘记sum
}
dfs(1, 0, 0);
cout << sum + M[(1 << n) - 1] << endl << "0 ";
for(int i = 1; i <= n; i ++)
{
if(ans[i] == 0) cout << i << " 0 ";
else if(ans[i] > i) cout << i << " " << ans[i] << " 0 ";
}
cout << endl;
return 0;
}
Written by \(\operatorname{Rosmarinus}\).
希望能对您有所帮助。

浙公网安备 33010602011771号