【FZYZOJ】愚人节礼物 题解(状压DP)
前言:麻麻我会写状压DP了!
----------------------------
题目描述
愚人节到了!可爱的UOI小朋友要给孩子们送礼物(汗~原题不是可爱的打败图么= =..)。在平面直角坐标系上(欧氏的),有 N (N≤17)个点,分别代表 N 个小朋友的地理位置;还有一个点,这是UOI的家,UOI的所有礼物都放在这里,UOI必须驼着礼物从这里出发,送完礼物以后再返回这里,然后再拿出礼物出发。UOI身上每多驼一个礼物,他的速度就减半。已知他不驼礼物的速度是 V。为了不耽误(你懂的),UOI必须尽快完成这个任务。求完成任务的最短时间。
输入格式
第1行 1 个正整数,表示小朋友的个数。
以下 N+1 行每行两个整数,除第一个表示UOI的家以外,其他都表示一个小朋友的坐标。
最后一行一个整数 V,表示UOI的速度。
输出格式
输出一个整数,表示最短时间。四舍五入。
----------------------------------------------------------
作为一道变形的状压DP题,其转移方程还是比较简单的(亏我好几个小时都没想到QAQ)。我们首先证明一个结论:
要保证方案的最优性,每次最多只能携带两个礼物。
证明:假设现在原点为$O$,平面上有$A,B,C$三个点。假如我们一次性走完,那么费用是$8OA+4AB+2BC+CO$;如果我们先走$A$点,再走$B$点和$C$点,那么费用是$3OA+4OB+2BC+CO$,两式相减,得$5OA+4AB-4OB=OA+4(OA+AB-OB)$,在三角形$OAB$中$OA+AB-OB>0$,所以相减之后的式子是大于$0$的。得证选三个点不优。对于大于三个点的情况,可以类比成多个三角形和线段组合成的图形,证明方法是类似的。证毕。复杂度$O(n^2 2^n)$
状态转移方程:假设$1$表示不选,$0$表示选,则有:
$f[k]=\min (f[k],\min(f[k|(1<<(i-1)|1<<(j-1)]+cost(i,j),f[k|1<<(i-1)]+cost(i))$
约束条件:
if (!1<<(i-1)&k&&!1<<(j-1)&k)
注意开$long\ long$!!!!!!!!!!!(被这个卡了好久QAQ)
代码:
#include<bits/stdc++.h> #define int long long using namespace std; int n,x[20],y[20],v; double f[1926817],ans,dis[20][20]; double cal(int a,int b) { return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b])); } double C1(int a,int b) { if (dis[0][a]<dis[0][b]) swap(a,b); return (4*dis[0][b]+2*dis[a][b]+dis[a][0])/v; } double C2(int i) { return 3*dis[i][0]/v; } signed main() { scanf("%lld",&n); scanf("%lld%lld",&x[0],&y[0]); for (int i=1;i<=n;i++) scanf("%lld%lld",&x[i],&y[i]); scanf("%lld",&v); for (int i=0;i<=n;i++) for (int j=i;j<=n;j++) dis[i][j]=dis[j][i]=cal(i,j); for (int i=(1<<n)-2;i>=0;i--) { f[i]=1e18; for (int j=1;j<=n;j++) { if ((1<<(j-1))&i) continue; for (int k=j+1;k<=n;k++) { if ((1<<(k-1))&i) continue; f[i]=min(f[i],f[i|(1<<(j-1))|(1<<(k-1))]+C1(j,k)); } f[i]=min(f[i],f[i|(1<<(j-1))]+C2(j)); } } printf("%.0lf",f[0]); return 0; }