Luogu 2831 [NOIP2016] 愤怒的小鸟
第一眼看成爆搜的状压dp,膜Chester大神犇。
考虑到三个不在同一直线上的点可以确定一条抛物线,而固定点$(0, 0)$和不在同一直线上这两个条件是题目中给定的,所以我们只要枚举两个点然后暴力算抛物线,然后chk一遍观察一下多少点在这一条抛物线上就行了。
想到状压之后状态和方程就显然了。
注意判解出来的抛物线$a \leqslant 0$的情况, eps开小一点。
因为决策集合的大小是$O(n ^ {2})$级别的,所以时间复杂度为$O(Tn^{2}2^{n})$。
Code:
#include <cstdio> #include <cstring> using namespace std; typedef double db; const int N = 20; const int S = (1 << 18) + 5; const int inf = 0x3f3f3f3f; const db eps = 1e-6; int testCase, n, m, q[N * (N + 1)], f[S]; bool vis[S]; struct Node { db x, y; } a[N]; inline db fabs(db x) { return x > 0 ? x : -x; } inline bool chk0(db x) { return fabs(x) < eps; } inline void chkMin(int &x, int y) { if(y < x) x = y; } inline int calc(int u, int v) { db a1 = a[u].x * a[u].x, b1 = a[u].x, c1 = a[u].y; db a2 = a[v].x * a[v].x, b2 = a[v].x, c2 = a[v].y; if(chk0((b1 * a2 - b2 * a1)) || chk0((a1 * b2 - b1 * a2))) return 0; db y = (c1 * a2 - c2 * a1) / (b1 * a2 - b2 * a1); db x = (c1 * b2 - c2 * b1) / (a1 * b2 - b1 * a2); if(x > 0 ||chk0(x)) return 0; int res = (1 << (u - 1)) | (1 << (v - 1)); for(int i = 1; i <= n; i++) { if(i == u || i == v) continue; if(chk0(x * a[i].x * a[i].x + y * a[i].x - a[i].y)) res |= (1 << (i - 1)); } return res; } int main() { for(scanf("%d", &testCase); testCase--; ) { scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) scanf("%lf%lf", &a[i].x, &a[i].y); m = 0; memset(vis, 0, sizeof(vis)); for(int i = 1; i <= n; i++) for(int j = 1; j < i; j++) { int s = calc(i, j); if(vis[s] || s == 0) continue; vis[s] = 1; q[++m] = s; } for(int i = 1; i <= n; i++) q[++m] = (1 << (i - 1)); /* for(int i = 1; i <= m; i++) printf("%d ", q[i]); printf("\n"); */ memset(f, 0x3f, sizeof(f)); f[0] = 0; for(int s = 0; s < (1 << n); s++) for(int i = 1; i <= m; i++) chkMin(f[s | q[i]], f[s] + 1); printf("%d\n", f[(1 << n) - 1]); } return 0; }