题目链接
题解
数据范围显然状压/爆搜。
考虑(f[S])表示二进制下已打了的猪的集合。
可以枚举(S)的子集(S_1),判定(S)中(S_1)的补集(S_2)是否合法。
判定可以通过待定系数法做到(O(n))判定。若补集合法,则(f[S]=min{f[S_1]+1 })。
复杂度是(O(Tn3^n))。这样能(70)分。
考虑如何优化。因为(n)很小,所以可以(O(n^3))预处理出(g_{i,j})表示经过(i,j)两点的二次函数可达的点集。那么转移的时候枚举二次函数(通过枚举点(i)和点(j)),转移方程(f[S|g_{i,j}]=min{f[S]+1})。
那么复杂度降到(O(Tn^22^n))。已经可以通过本题了。
唐神的(blog)里面还有个优化。
一个结论:每个状态(S)用于转移的二次函数,一定经过该状态中不包含的第一个点(x)。因为最后的目标是选所有的点,这个点不选,在最后的最优方案中也一定会被其他点选到,而前面已经处理出了(g_{i,j}),可达点集已经都处理出来了。所以只用这个点(x)转移是合法的。
如果预处理出来每个状态(S)所对应的(x)的话复杂度就是(O(Tn2^n))的。如果不处理的话会慢一点但也不会太多。
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
const int inf = 0x3f3f3f3f;
const double eps = 1e-10;
int n, m, f[(1 << 18) + 5], T, g[N][N];
double a[N], b[N];
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; ++i) scanf("%lf%lf", &a[i], &b[i]);
memset(g, 0, sizeof(g)); memset(f, 0x3f, sizeof(f)); f[0] = 0;
for(int i = 0; i < n; ++i) {
for(int j = i + 1; j < n; ++j) {
if(a[i] == a[j]) continue;
double A = (b[i] - b[j] * a[i] / a[j]) / (a[i] * a[i] - a[i] * a[j]);
double B = (b[i] - A * a[i] * a[i]) / a[i];
if(A >= 0) continue;
for(int k = 0; k < n; ++k)
if(fabs(b[k] - (A * a[k] * a[k] + B * a[k])) <= eps) g[i][j] |= 1 << k;
}
}
for(int S = 0; S < 1 << n; ++S) {
for(int i = 0; i < n; ++i) {
f[S | (1 << i)] = min(f[S | (1 << i)], f[S] + 1);
if(!(S & (1 << i))) {
for(int j = i + 1; j < n; ++j)
f[S | g[i][j]] = min(f[S | g[i][j]], f[S] + 1);
break;
}
}
}
printf("%d
", f[(1 << n) - 1]);
}
}