P2831 愤怒的小鸟

P2831 愤怒的小鸟

题意:

一个平面上有 \(n\) 个在第一象限的点,求至少要多少个解析式为 \(y=ax^2+bx\) 的开口向下的抛物线(即 \(a<0\))才能覆盖这 \(n\) 个点。

其中,\(n\le18\)

分析:

先来看如何求抛物线解析式。已知两点 \((x_1,y_1),(x_2,y_2)\),求经过这两个点的抛物线解析式 \(y=ax^2+bx\) 的系数。

由抛物线 \(y=ax^2+bx\) 过点 \((x_1,y_1),(x_2,y_2)\) 得:\(\begin{cases}y_1=ax_1^2+bx_1\\y_2=ax_2^2-bx_2\end{cases}\)

\(\begin{cases}x_2y_1=ax_1^2x_2+bx_1x_2\\x_1y_2=ax_1x_2^2+bx_1x_2\end{cases}\)

由上面的式子减去下面的式子得:

\(x_2y_1-x_1y_2=a(x_1^2x_2-x_1x_2^2)=ax_1x_2(x_1-x_2)\)
\(\therefore a=\dfrac{x_2y_1-x_1y_2}{x_1x_2(x_1-x_2)}\)
\(\because y_1=ax_1^2+bx_1\)
\(\therefore b=\dfrac{y_1-ax_1^2}{x_1}\)
\(\because x_1\ne0\)
\(\therefore b=\dfrac{y_1}{x_1}-ax_1\)
\(\therefore\begin{cases}a=\dfrac{x_2y_1-x_1y_2}{x_1x_2(x_1-x_2)}\\b=\dfrac{y_1-ax_1^2}{x_1}\end{cases}\)

我们预处理出每两个点所在的抛物线,并用 \(n\) 位二进制数记录抛物线上有哪些点(将经过点 \(i,j\) 的抛物线上的点用 \(c_{i,j}\) 来记录),复杂度为 \(O(n^3)\)

\(f_i\) 表示这些点是否已经被覆盖(\(i\) 的第 \(j\) 个二进制位用 \(1/0\) 表示第 \(j\) 个点是/否已经被覆盖)。每次枚举集合 \(i\),其中 \(i\in[0,2^n)\),并枚举两个点 \(j,k\),表示集合 \(i\) 是由一个集合加上 \(j,k\) 所在的抛物线的贡献转移而来的,即 \(f_i=f_{i\cap(c_{j,k}的补集)}+1\)

按照如上的思路,复杂度为 \(O(T(n^3+2^nn^2))\)\(T\) 为数据组数),第20个测试点达到了 \(4\times10^8\) 的级别,会 \(T\)

考虑优化。设 \(x\) 为集合 \(i\) 中的第一个没被经过的点,可以发现无论当前状态下无论是否选择该点,以后都得选,所以会出现一些重复无用的状态。于是我们每次枚举集合 \(i\) 时,就强制选择集合中第一个没被经过的点,因此只需枚举另一个点即可转移状态。

设集合 \(i\) 中第一个没被经过的点为 \(pos\),枚举一个点 \(j(j\in[1,n])\),状态转移为 \(f_i=f_{i\cap(c_{pos,j}的补集)}\),复杂度为 \(O(T(n^3+2^nn))\)

因为可能一个点单独被覆盖更优的情况,因此每次要单独转移只覆盖点 \(pos\) 的情况。

注意转移时,并非只有 \(c_{pos,j}\in i\) 时才转移,因为可能会出现抛物线相交,导致一条抛物线上的点会被其它抛物线覆盖,但仍需选择这条抛物线去覆盖点的情况。

Code:

#include <bits/stdc++.h>
using namespace std;

const int N = 20, M = 1e6 + 10;
const double eps = 1e-8;

int _, n, m, f[M], bit[M], c[N][N];
double a, b, x[N], y[N];

inline int read(){
	int s = 0, w = 1;
	char ch = getchar();
	for (; ch < '0' || ch > '9'; w *= ch == '-' ? -1 : 1, ch = getchar());
	for (; ch >= '0' && ch <= '9'; s = s * 10 + ch - '0', ch = getchar());
	return s * w;
}

int lowbit(int x){
	return (x & 1) ? 1 : (lowbit(x >> 1) + 1);
}

void calc(double x1, double y1, double x2, double y2){
	a = (x2 * y1 - x1 * y2) / x1 / x2 / (x1 - x2);
	b = y1 / x1 - a * x1;
}

signed main(){
	for (int i = 1; i < (1 << 18); ++i) bit[i] = lowbit(i);
	for (scanf("%d", &_); _--; ){
		memset(c, 0, sizeof(c));
		memset(f, 0x3f, sizeof(f));
		f[0] = 0;
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; ++i) scanf("%lf%lf", &x[i], &y[i]);
		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= n; ++j){
				if (fabs(x[i] - x[j]) < eps) continue;
				calc(x[i], y[i], x[j], y[j]);
				if (a > -eps) continue;
				for (int k = 1; k <= n; ++k)
					if (fabs(a * x[k] * x[k] + b * x[k] - y[k]) < eps) c[i][j] |= (1 << (k - 1));
			}
		for (int i = 1; i < (1 << n); ++i){
			f[i] = min(f[i], f[i & (~(1 << (bit[i] - 1)))] + 1);
			for (int j = 1; j <= n; ++j) f[i] = min(f[i], f[i & (~c[bit[i]][j])] + 1);
		}
		printf("%d\n", f[(1 << n) - 1]);
	}
	return 0;
}
posted @ 2022-08-18 18:45  leoair  阅读(21)  评论(0)    收藏  举报