题目描述
有 n n n 个城市, m m m 个箱子,每个箱子里面都有一个加速器,经过每个加速器可以将速度变为原来的 2 2 2 倍,现在需要从原点出发中间必须经过 n n n 个城市和任意个箱子,求最终回到原点的最短时间
输入样例
2 1
1 1
0 1
1 0
输出样例
2.5000000000
算法
(状态压缩dp) O ( 2 n + m ∗ ( n + m ) 2 ) O(2^{n+m} * (n+m)^2) O(2n+m∗(n+m)2)
状态表示
d
p
[
i
]
[
s
]
dp[i][s]
dp[i][s] 走的路线为
s
s
s,并且现在在点
i
i
i 的时间的最小值
其中
s
s
s 表示的路径 一共有
m
+
n
m+n
m+n 位,从右往左
[
0
,
n
−
1
]
[0,n-1]
[0,n−1] 位表示城市,
[
n
,
n
−
m
+
1
]
[n,n-m+1]
[n,n−m+1] 位表示加速器
初始化
for(int i=0;i<n+m;i++)
{
for(int s=0;s<(1<<(n+m));s++)
{
dp[i][s]=DBL_MAX;
}
}
//初始化 从原点到每个点的距离
for(int i=0;i<n+m;i++)
{
dp[i][(1<<i)]=dis(x[i],y[i]);
}
for(int i=0;i<n+m;i++)dp[i][1<<i]=dis(x[i],y[i]);
状态转移
假设现在从第
i
i
i 个城市/加速器 走到第
j
j
j 个城市/加速器所需要的时间为
1.
计算
i
i
i ,
j
j
j 之间的距离
d
i
s
dis
dis
2.
计算这条路线上的速度
v
v
v
v
v
v
=
=
=
2
加速器数量
2^{加速器数量}
2加速器数量
那么加速器的数量
=
=
=
s
>
>
n
s>>n
s>>n 中
1
1
1 的个数(预处理出来)
所以经过这一段所需要的时间
t
=
d
i
s
/
v
t = dis/v
t=dis/v
预处理
for(int s=0;s<M;s++)
{
for(int j=0;j<N;j++)
{
if((s>>j)&1) two[s]++;
}
}
状态转移
for(int s=1;s<(1<<(n+m));s++)//枚举已经走过的点
{
int cnt=two[(s>>n)];//看经过了多少个加速器
double v=(1<<cnt);//速度
//这次走从i到j
for(int i=0;i<n+m;i++)
{
if((s>>i)&1)//上一次走到了i
{
for(int j=0;j<n+m;j++)
{
if(((s>>j)&1)==0)//上一次没有走到j
{
dp[j][s^(1<<j)]=min(dp[j][s^(1<<j)],dp[i][s]+dis(x[i]-x[j],y[i]-y[j])/v);
}
}
}
}
}
最终结果
由于最终还需要走回到原点,所以要对于所有已经走过 n n n 个城市的路径 s s s 再次计算到原点所需要花销的时间,具体细节跟上面计算方法一样
C++ 代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<unordered_map>
#include<unordered_set>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<vector>
#include<deque>
#include<cmath>
#include<ctime>
#include<cfloat>
using namespace std;
double res=DBL_MAX;
const int N=20,M=(1<<20);
typedef pair<int,int> pii;
#define x first
#define y second
int x[N],y[N];
double dp[N][M];
int two[M];
int n,m;
double dis(double x,double y)
{
double t=x*x+y*y;
return sqrt(t);
}
int main()
{
cin>>n>>m;
for(int i=0;i<n+m;i++) scanf("%d %d",&x[i],&y[i]);
for(int i=0;i<n+m;i++)
{
for(int s=0;s<(1<<(n+m));s++)
{
dp[i][s]=DBL_MAX;
}
}
for(int s=0;s<M;s++)
{
for(int j=0;j<N;j++)
{
if((s>>j)&1) two[s]++;
}
}
//初始化 从原点到每个点的距离
for(int i=0;i<n+m;i++)
{
dp[i][(1<<i)]=dis(x[i],y[i]);
}
for(int i=0;i<n+m;i++)dp[i][1<<i]=dis(x[i],y[i]);
for(int s=1;s<(1<<(n+m));s++)//枚举已经走过的点
{
int cnt=two[(s>>n)];//看经过了多少个加速器
double v=(1<<cnt);//速度
//这次走从i到j
for(int i=0;i<n+m;i++)
{
if((s>>i)&1)//上一次走到了i
{
for(int j=0;j<n+m;j++)
{
if(((s>>j)&1)==0)//上一次没有走到j
{
dp[j][s^(1<<j)]=min(dp[j][s^(1<<j)],
dp[i][s]+dis(x[i]-x[j],y[i]-y[j])/v);
}
}
}
}
}
double res=DBL_MAX;
for(int i=0;i<n+m;i++)
{
for(int s=(1<<n)-1;s<((1<<(n+m)));s++)
{
bool f=0;
//检查是不是后n位都为1
for(int k=0;k<n;k++)
{
if(((s>>k)&1)==0)
{
f=1;
break;
}
}
if(f) continue;
int cnt=two[(s>>n)];//看经过了多少个加速器
double v=(1<<cnt);//速度的倒数
res=min(res,dp[i][s]+dis(x[i],y[i])/v);
}
}
printf("%.10lf",res);
}