[NOIP2016] 愤怒的小鸟 题解

题面:

题解:

一看到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是否能构成一条抛物线。
判断法则:

  1. 如果i与j横坐标相同的话,那就不可能构成一条抛物线。
  2. 由于抛物线都是形如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\n",f[(1<<n)-1]);
42     }
43     return 0;
44 }

猜你喜欢

转载自www.cnblogs.com/Asika3912333/p/11366417.html