题目链接
题目求的是:给定一些点,求最少用几条抛物线覆盖所有的点。
由于N很小,且抛物线过原点,因此只需要2个点就能确定一条抛物线。可以预先处理出所有可能的抛物线和经过的点,因此这题就变成重复覆盖问题。可以考虑用DLX。
这边简单起见,用状压DP的方法,每个点被覆盖或没被覆盖看作一个状态1或0。
例如s = 1011
,代表第1个点、第2个点、第4个点被覆盖了
f[s] = f[s'] + 1
,对于每个s'
,枚举其中没覆盖的点x,并选择覆盖x的抛物线进行覆盖,设抛物线覆盖的点的状态是p[x, j]
,则s = s' | p[x, j]
。
计算抛物线如下:
ax12 + bx1 = y1 --> ax1 + b = y1 / x1
ax22 + bx2 = y2 --> ax2 + b = y2 / x2
俩式相减
a(x1 - x2) = y1 / x1 - y2 / x2
综上
a = (y1 / x1 - y2 / x2) / (x1 - x2)
b = y1 / x1 - ax1
注意a < 0
#include <iostream>
#include <cmath>
#include <cstring>
#define x first
#define y second
using namespace std;
const double eps = 1e-8;
int t, n, m, lp;
int f[1 << 18], par[20][20];
typedef pair<double, double> coo;
coo pig[20];
int main() {
scanf("%d", &t);
while(t -- ) {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++ ) {
scanf("%lf%lf", &pig[i].x, &pig[i].y);
}
lp = 0;
memset(par, 0, sizeof(par));
for (int i = 0; i < n; i ++ ) {
par[i][i] = 1 << i;
for (int j = 0; j < n; j ++ ) {
if (i != j && pig[i].x - pig[j].x > eps) {
double a = (pig[i].y / pig[i].x - pig[j].y / pig[j].x) / (pig[i].x - pig[j].x);
if (a >= 0) continue;
double b = pig[i].y / pig[i].x - a * pig[i].x;
int state = 0;
for (int k = 0; k < n; k ++ ) {
if (abs(a * pig[k].x * pig[k].x + b * pig[k].x - pig[k].y) < eps) {
state += 1 << k;
}
}
par[i][j] = par[j][i] = state;
}
}
}
memset(f, 0x3f, sizeof(f));
f[0] = 0;
for (int i = 0; i < 1 << n; i ++ ) {
int x = 0;
for (int j = 0; j < n; j ++ ) {
if( !((i >> j) & 1) ) { // 枚举没覆盖的点
x = j;
break;
}
}
for (int j = 0; j < n; j ++ ) {
f[i | par[x][j]] = min(f[i | par[x][j]], f[i] + 1);
}
}
printf("%d\n", f[(1 << n) - 1]);
}
return 0;
}