题面:
题解:
一看到n≤18,我们自然就会想到状压DP,设 f[i] 表示i的二进制表示下1已经打到的小猪与0还未打到的小猪所需要的最少小鸟数,答案就在 f[(1<<n)-1]里。
再设S[i][j]其值的二进制下为1的表示i与j所形成的抛物线能够打到的小猪,0表示打不到的小猪。
那么自然有:
f[i|S[i][j]]=min(f[i|S[i][j],f[i]+1)
考虑如何求出S[i][j],我们先枚举出i与j,判断i与j是否能构成一条抛物线。
判断法则:
- 如果i与j横坐标相同的话,那就不可能构成一条抛物线。
- 由于抛物线都是形如y=ax2+bx的,所以我们带入i,j的横纵坐标,就得到一个二元一次方程组,用通式解出a,b然后再判断a是否小于0。
然后枚举小猪k(1<=k<=n)的坐标,检验是否再抛物线上,若在则S[i][j]=S[i][j]&(1<<k-1)。
优化:我们只需要找出这一个状态中还未被打到的最小的小猪,并用这个小猪去扩展其他点,因为反正最后都要打这一个点现在打了就好,防止以后重复多次去打这同一个点。
附上代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const double eps=1e-8; 4 int t,n,m,S[20][20],f[1<<20]; 5 double x[20],y[20]; 6 7 //解方程 8 void equation(double &x,double &y,double a1,double b1,double c1,double a2,double b2,double c2){ 9 y=(a1*c2-a2*c1)/(a1*b2-a2*b1); 10 x=(c1-b1*y)/a1; 11 } 12 13 int main(){ 14 for(scanf("%d",&t);t;t--){ 15 //初始化 16 memset(f,0x3f,sizeof(f)); 17 f[0]=0; 18 scanf("%d %d",&n,&m); 19 for(int i=1;i<=n;++i) scanf("%lf %lf",&x[i],&y[i]); 20 //枚举两个小猪 21 for(int i=1;i<=n;++i){ 22 for(int j=1;j<=n;++j){ 23 if(fabs(x[i]-x[j])<eps) continue; 24 double a,b; 25 equation(a,b,x[i]*x[i],x[i],y[i],x[j]*x[j],x[j],y[j]); 26 if(a>-eps) continue; 27 //枚举其他小猪 28 for(int k=1;k<=n;++k) 29 if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<eps) S[i][j]|=(1<<(k-1));//判断是否在抛物线上 30 } 31 } 32 //dp 33 for(int i=0;i<(1<<n);++i){ 34 //j表示第一个没有被打的小猪 35 int j=0; 36 for(j=1;j<=n-1;++j) 37 if(!(i&(1<<j-1))) break; 38 f[i|(1<<(j-1))]=min(f[i|(1<<(j-1))],f[i]+1);//先单独转移,防止出现不与其他小猪构成抛物线的情况 39 for(int k=1;k<=n;++k) f[i|S[j][k]]=min(f[i|S[j][k]],f[i]+1); 40 } 41 printf("%d ",f[(1<<n)-1]); 42 } 43 return 0; 44 }