【题解】P8733 题解

P8733 题解

看到这种类似“旅行商问题”的题目时,我们可以考虑使用搜索,但是搜索很慢,所以考虑使用状压动态规划。

众所周知,状压有两种求解方法,一种是“人人为我”(用别人去更新自己),一种是“我为人人”(用自己去更新别人)。这里以“我为人人”为例。

设当前状态为 SS ,则 f(S,i)f(S,i) 为当前状态为 SS,且最后一步选了 ii 的最短距离。那么“我为人人”就要考虑往当前状态上加一个结点。

不妨设为加的这个点为 jj,首先 jj 不能在 SS 中(显然你不能走重复的结点),然后我们让 S 加入点 jj,不妨设加入后状态为 SS',那么有如下转移方程:

f(S,j)=min{f(S,j),f(S,i)+dis(i,j)}f(S', j)=\min\{f(S', j), f(S,i)+dis(i,j)\}

其中 dis(i,j)dis(i,j) 表示 (i,j)(i,j) 之间的“距离”,根据题意,我们可以做如下操作来计算这个“距离”:

  • 如果两点间的欧几里得距离为 rr,且 rdr \le d,那么显然这两个点之间可以通行连边,并设置权值为 rr

  • 否则,两个点不能通,设置权值为 \infty

然后跑 Floyd 即可,跑完后的数组就是 dis(i,j)dis(i,j)

注意 ii 要在 SS 中,可以枚举 11nn 然后逐一判断。

答案为 min{f(T,i)+dis(i,1)}\min\{f(T, i)+dis(i,1)\},其中 1in1\le i \le nTT11nn 都选的状态。

最后给出本题要用到的三个二进制的知识点:

  • S | (1 << (x - 1)) 表示给状态 SS 加入元素 xx

  • S & (1 << (x - 1)) 表示判断 xx 是否在 SS 中。如果在,值为 11,否则为 00

  • (1 << n) - 1 表示 11nn 都选的状态。

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 21;
const int M = 1048576 + 5;

int n, d;
int x[N], y[N]; // 点的坐标
double dp[N][N]; // dis 数组
double f[M][N]; 

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> d;
    for(int i = 1;i <= n;i++)
    {
    	cin >> x[i] >> y[i];
	}
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= n;j++)
		{
			double dis = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j])); //计算欧几里得距离
            //根据上述方式“建图”
			if(dis <= d) dp[i][j] = dis;
			else dp[i][j] = 1e7; 	
            
		}		
	} 
    //跑 Floyd
	for(int k = 1;k <= n;k++)
	{
		for(int i = 1;i <= n;i++)
		{
			for(int j = 1;j <= n;j++)
			{
				dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
			}
		}
	}
	fill(*f, *f + M * N, 1e7);
	f[1][1] = 0;
	for(int S = 0;S < (1 << n);S++)
	{
		for(int i = 1;i <= n;i++)
		{
			if(S & (1 << (i - 1))) // i 得在 S 中
			{
				for(int j = 1;j <= n;j++)
				{
					if(S & (1 << (j - 1))) continue; //j 如果在 S 中,舍
					f[(S | (1 << (j - 1)))][j] = min(f[(S | (1 << (j - 1)))][j], f[S][i] + dp[i][j]); // 更新
				}
			}
		}
	}
	double ans = 0x7fffffff;
	for(int i = 1;i <= n;i++)
	{
		ans = min(ans, f[(1 << n) - 1][i] + dp[i][1]); //计算答案
	}
	cout << fixed << setprecision(2) << ans << endl;
    return 0;
}

posted @ 2023-12-15 14:01  邻补角-SSA  阅读(18)  评论(0)    收藏  举报  来源