f(s)表示消灭s集合的小猪所需要的最小数量,考虑到n很小,最大才18,,用二进制来表示状态,0为第i个只小猪没有被消灭,1为第i只小猪被消灭了,就像010001010101这样的,最后求的就是111111111111111这样状态的值
P[i]表示所有可能的抛物线
需要注意的是,每条抛物线能够干掉的小猪数分为两种情况
- 抛物线干掉了1只小猪
- 抛物线干掉了2只或以上小猪
因为题目中说了只能发射 a < 0的抛物线,因此有时候我们通过两只小猪确定的抛物线不能收入备选集合,如果说不保留干掉1只小猪的抛物线,最后有可能不能通过备选集合干掉所有小猪,所以在枚举时我们不能把只含有一只小猪的抛物线覆盖掉
状态转移方程为
f
(
S
∣
P
[
i
]
)
=
m
i
n
(
f
(
S
∣
P
[
i
]
)
,
f
(
S
)
+
1
)
f(S|P[i])=min(f(S|P[i]),f(S)+1)
f(S∣P[i])=min(f(S∣P[i]),f(S)+1)
通过枚举两个点来确定一个抛物线,然后再去一个个检查还有哪些小猪是位于这条抛物线上的,这样确定好一条抛物线能够消灭的小猪集合,和目前的状态取并集,最后就能推出全集的答案
然后需要注意的就是精度问题了…
题目的精度只有两位,而显然都是小数的话,是他们没法出那种正好带入方程
y
−
a
x
2
−
b
x
y-ax^2-bx
y−ax2−bx 答案就是0的点,这时候可以定义一个比较小的数,Eps(也不能太小) 当算出的数比这个数还要小的时候,就可以认为这个数是0。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define DEBUG(x) std::cerr<<#x<<"="<<x<<std::endl;
const int maxp = 1 << 18 + 1;
const int maxn = 19;
const double Eps = 1e-6;
int n,t,f[maxp],p[19*19],size,m;
struct Point{
double x, y;
}point[maxn];
int main() {
scanf("%d", &t);
while(t--) {
memset(p, 0, sizeof(p)), size = 0;
memset(f, 0x3f, sizeof(f));
f[0] = 0;//边界
scanf("%d %d",&n, &m);
for(int i=1; i<=n; i++) {
scanf("%lf %lf", &point[i].x, &point[i].y);
}
for(int i=1; i<=n; i++) {
p[++size] = (1 << (i-1));//这里保留了只消灭一只小猪的抛物线
for(int j=i+1; j<=n; j++) {
double x1 = point[i].x, y1 = point[i].y, x2 = point[j].x, y2 = point[j].y;
double a = (x1 * y2 - x2 * y1) / (x1 * x2 * (x2 - x1));
double b = (y1 * x2 * x2 - y2 * x1 * x1) / (x1 * x2 * (x2 - x1));
if(a > -Eps) continue; //抛物线的开口要向下...
size++;
for(int k=1; k<=n; k++) {
double kx = point[k].x, ky = point[k].y;
if(fabs(ky-(a*kx*kx+b*kx)) <= Eps) p[size] |= (1<<(k-1));
}
}
}
for(int s=0; s<(1<<n); s++) {
for(int i=1; i<=size; i++) {
f[s|p[i]] = std::min(f[s|p[i]], f[s]+1);
}
}
printf("%d\n",f[(1<<n)-1]);
}
}