题目
Description
小蓝是一个直升飞机驾驶员,他负责给山区的 nn 个村庄运送物资。
每个月,他都要到每个村庄至少一次,可以多于一次,将村庄需要的物资运送过去。
每个村庄都正好有一个直升机场,每两个村庄之间的路程都正好是村庄之间的直线距离。
由于直升机的油箱大小有限,小蓝单次飞行的距离不能超过 DD。每个直升机场都有加油站,可以给直升机加满油。
每个月,小蓝都是从总部出发,给各个村庄运送完物资后回到总部。如果方便,小蓝中途也可以经过总部来加油。
总部位于编号为 11 的村庄。
请问,要完成一个月的任务,小蓝至少要飞行多长距离?
Input
输入的第一行包含两个整数 nn,DD,分别表示村庄的数量和单次飞行的距离。
接下来 nn 行描述村庄的位置,其中第 ii 行两个整数 xixi,yiyi 分别表示编号为 ii 的村庄的坐标。村庄 ii 和村庄 jj 之间的距离为 欧几里得距离。
Output
输出一行,包含一个实数,四舍五入保留正好 22 位小数,表示答案。
Sample Input
4 6 1 1 4 5 8 5 11 1
Sample Output
28.00
思路
这是一道状压$dp$的经典题
但不同的是每个村庄可以经过多次;
我们首先考虑什么情况下需要多次经过;
很明显如果两点距离大于$D$时,无法直接到达,就需要通过其他点去到达目标点,而在这个过程中其他点可能已经被经过了;
对于多次经过的点,记录状态十分困难;
所以,我们可以提前用最短路预处理每个点到其他点的最短距离;
这样记录状态时就可以忽略中间重复经过的点而直接记录包含目标点的状态集合;
那么状态转移方程很显然就是:
$dp[i][k]=min(dp[i][k],dp[j][k \oplus (1<<(i-1))]+a[j][i]);$
$k$ 是枚举的到达过的点的集合,$i$ 和 $j$ 都是枚举当前到达的点;
代码
#include<bits/stdc++.h> typedef long long ll; using namespace std; const ll _=21; ll n; double m; double b[_],c[_]; double a[_][_],dp[_][1<<20]; int main() {memset(dp,127,sizeof(dp));scanf("%lld%lf",&n,&m);for(ll i=1;i<=n;i++)scanf("%lf%lf",&b[i],&c[i]);for(ll i=1;i<=n;i++)for(ll j=1;j<=n;j++){double num=sqrt(pow(b[i]-b[j],2)+pow(c[i]-c[j],2));if(num<=m)a[i][j]=num;elsea[i][j]=INT_MAX;}for(ll k=1;k<=n;k++)for(ll i=1;i<=n;i++)for(ll j=1;j<=n;j++)//最短路预处理if(a[i][j]>a[i][k]+a[k][j])a[i][j]=a[i][k]+a[k][j];dp[1][1]=0;//从第一个村庄出发for(ll k=1;k<=(1<<n)-1;k+=2)//k每次加二保证k集合永远包含第一个村庄for(ll i=1;i<=n;i++)for(ll j=1;j<=n;j++)if(k&(1<<(i-1))&&k&(1<<(j-1))){dp[i][k]=min(dp[i][k],dp[j][k^(1<<(i-1))]+a[j][i]);//转移 }double ans=INT_MAX;for(ll i=2;i<=n;i++)ans=min(ans,dp[i][(1<<n)-1]+a[i][1]);printf("%.2lf",ans);return 0; }