题解 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}\).

希望能对您有所帮助。

posted @ 2021-04-16 20:44  XJ21  阅读(97)  评论(0)    收藏  举报