原题https://www.luogu.com.cn/problem/P1433
Description
房间里放着 n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0)点处。
Input
第一行有一个整数,表示奶酪的数量 n。
第 2 到第(n+1) 行,每行两个实数,第(i+1) 行的实数分别表示第 i 块奶酪的横纵坐标xi,yi。
Output
输出一行一个实数,表示要跑的最少距离,保留 2 位小数。
Sample 1
Input | Output |
---|---|
4 1 1 1 -1 -1 1 -1 -1 | 7.41 |
Hint
数据规模与约定
对于全部的测试点,保证 1≤n≤15,∣xi∣,∣yi∣≤200,小数点后最多有 2 位数字。
提示
解题思路
状态dp: 适用范围 N≤21
状态压缩的思想是用二进制来表示状态。用一个整数的二进制形式的每一个二进制位 0 或 1 表示一个状态。
在这里表示n块奶酪,就代表有n位二进制,每一位2进制代表一块奶酪。
例:100110 代表第2、3、6块奶酪已经吃到。
111111 代表所有奶酪吃到。
状态转移方程:Fi,k=min(Fi,k,Fj,k−2i−1+Ai,j)Fi,k=min(Fi,k,Fj,k−2i−1+Ai,j)
Fj,k−2i−1 为在 j 点且没有走过 i 点的最短距离, Ai,j 是从 i 到 j 的距离。
AC代码
状态dp
#include <iostream>
using namespace std;
#include <cstring>
#include <cmath>
#include <algorithm>
int n;
double a[16][16],x[16],y[16],f[16][33000];
//计算距离
double distance(int v,int w){
return sqrt((x[v]-x[w])*(x[v]-x[w])+(y[v]-y[w])*(y[v]-y[w]));
}
int main(){
cin>>n;
x[0]=y[0]=0;
for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
memset(f,127,sizeof(f));
//计算每块奶酪直接的距离
for(int i=0;i<=n;i++){
for(int j=i+1;j<=n;j++) a[i][j]=a[j][i]=distance(i,j);
}
//初始化每块奶酪的距离
for(int i=1;i<=n;i++) f[i][1<<(i-1)]=a[0][i];
for(int k=1;k<(1<<n);k++){
for(int i=1;i<=n;i++){
if((k&(1<<(i-1)))==0) continue; //没走过的路跳过
for(int j=1;j<=n;j++){
if(i==j) continue; //相等直接跳过
if((k&(1<<(j-1)))==0) continue; //没走过的路跳过
f[i][k]=min(f[i][k],f[j][k-(1<<(i-1))]+a[i][j]);
}
}
}
double ans=f[0][0];
for(int i=1;i<=n;i++) ans=min(ans,f[i][(1<<n)-1]);
printf("%.2lf",ans);
}
dfs(超时)
#include<iostream>
#include <cmath>
#include <algorithm>
using namespace std;
int n,flag[20];
double x[20],y[20],a[20][20];
bool vis[20];
double Min=0x3ffffffffffff;
void dfs(int k,double d){
if(d>Min) return ;
if(k>n){
Min=d;
return;
}
for(int i=1;i<=n;i++){
if(vis[i]==0){
vis[i]=1;
flag[k]=i;
dfs(k+1,d+a[flag[k-1]][flag[k]]);
vis[i]=0;
}
}
}
int main(){
cin>>n;
x[0]=y[0]=0;
for(int i=1;i<=n;i++) {
cin>>x[i]>>y[i];
}
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
a[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
}
dfs(1,0.0);
printf("%.2lf\n",Min);
}