Link:
Solution:
其实是一道裸爆搜都能过的题目……
看到$nle 18$想到状压$dp$
令$dp[i]$,表示达到当前状态的最小代价
可以发现只要两个点就能确定一条从原点出发的抛物线
因此每次对于一个特定的状态枚举两点确定抛物线转移就好了
$O(n^3)$预处理出每两个点形成的抛物线能经过的状态$f[i][j]$,接下来$O(n^2*2^n)$转移
但每次一定要$O(n^2)$枚举所有的点对吗?
其实完全可以通过构造有序性来去掉一个$n$:
默认从小到大打猪,每次都打最小未被打的,毕竟先打了后面的也要再回头打前面的
这样就确定了点对中的第一个点:最小未被打的点
只要枚举另一个点就将复杂度降到了$O(n*2^n)$
Code:
#include <bits/stdc++.h> using namespace std; const int MAXN=1<<18,INF=1<<27; const double eps=1e-6; double x[20],y[20]; int T,n,m,dp[MAXN],f[20][20]; int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=0;i<n;i++) scanf("%lf%lf",&x[i],&y[i]); memset(f,0,sizeof(f)); for(int i=0;i<n;i++) for(int j=0;j<n;j++) { if(i==j){f[i][j]=(1<<i);continue;} double a=(y[j]/x[j]-y[i]/x[i])/(x[j]-x[i]); double b=y[i]/x[i]-a*x[i]; if(a>-eps) continue; for(int k=0;k<n;k++) if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=eps) f[i][j]|=(1<<k); } for(int i=0;i<MAXN;i++) dp[i]=INF; int fst;dp[0]=0; for(int i=0;i<(1<<n);i++) { if(dp[i]==INF) continue; for(fst=0;fst<n;fst++) if(!(i&(1<<fst))) break; for(int j=0;j<n;j++) dp[i|f[fst][j]]=min(dp[i|f[fst][j]],dp[i]+1); } printf("%d ",dp[(1<<n)-1]); } return 0; }
Review:
学会利用构造有序性的方式减掉一维的枚举