P2831 [NOIP2016 提高组] 愤怒的小鸟
Problem
平面上有\(n\)个在第一象限的点,求至少要用几个类似于\(y = ax^2 + bx(a < 0,a,b \in \mathbb{R})\)的抛物线能将其全部覆盖。
\(n \le 18\)
Solution
Thinking 1
\(1 \le n \le 18\),状压dp.
设\(dp[S]\)为S状态下至少要用几个抛物线。
Thinking 2
发现抛物线必经\((0,0)\)。所以再选两点即可确定抛物线。
具体来说:现在有两个点\((x_1,y_1),(x_2,y_2)\),那么得:
\[\begin{cases}
ax_1^2 + bx_1 = y_1\\
ax_2^2 + bx_2 = y_2\\
\end{cases}
\]
1式乘上\(x_2\),2式乘上\(x_1\),得
\[ax_1^2x_2 + bx_1x_2 = y_1x_2\\
ax_2^2x_1 + bx_1x_2 = y_2x_1
\]
相减
\[a(x_1^2x_2 - x_2^2x_1) = y_1x_2 - y_2x_1\\
a = \dfrac{y_1x_2 - y_2x_1}{x_1^2x_2 - x_2^2x_1}
\]
\(b\)同理吧。
Thinking 3
考虑如何求\(dp[S]\):
- 若\(dp[S]\)的个数<2,则$dp[S] = $个数。
- otherwise,\(dp[S] = \min\{dp[S - s]\} + 1\)
其中\(s\)是指:
在\(S\)中选两点\((x_i,y_i),(x_j,y_j)\)
首先判断其确定的表达式中的\(a\)是否小于0
然后求出\(k\),满足\(i \neq j \neq k\)且\((x_k,y_k)\)在这个抛物线,\(s\)加上\(2^{k - 1}\)
注意s要加入i,j
做完了。
Thinking 4
其实我感觉用那个三点式和三分瞎搞搞也是能做的。
# include <bits/stdc++.h>
using namespace std;
# define int long long
const int N = 22;
const double eps = 1e-8;
int T,n,m;
struct node
{
double x,y;
node() {}
node(double _x,double _y) : x(_x),y(_y) {}
}a[N];
int dp[(1 << 20) + 5];
double f(double x,double x1,double x2,double x3,double y1,double y2,double y3)
{
return (((x - x2) * (x - x3)) / ((x1 - x2) * (x1 - x3))) * y1 + (((x - x1) * (x - x3)) / ((x2 - x1) * (x2 - x3))) * y2 + (((x - x1) * (x - x2)) / ((x3 - x1) * (x3 - x2))) * y3;
}
void equ(double &a,double &b,double x1,double x2,double y1,double y2)
{
a = -((y1 * x2 - y2 * x1) / (x2 * x2 * x1 - x1 * x1 * x2));
b = ((y1 * x2 * x2) - (y2 * x1 * x1)) / (x1 * x2 * x2 - x1 * x1 * x2);
return;
}
signed main(void)
{
// freopen("2831.in","r",stdin);
// freopen("2831.out","w",stdout);
scanf("%lld",&T);
while(T--)
{
scanf("%lld%lld",&n,&m);
for(int i = 1; i <= n; i++)
{
scanf("%lf%lf",&a[i].x,&a[i].y);
}
for(int i = 0; i < (1 << n); i++)
{
dp[i] = 0;
for(int j = 1; j <= n; j++)
{
if(i >> (j - 1) & 1)
{
++dp[i];
}
}
// printf("dp[%lld] = %lld\n",i,dp[i]);
}
for(int S = 0; S < (1 << n); S++)
{
int s = 0;
for(int i = 1; i <= n; i++)
{
for(int j = i + 1; j <= n; j++)
{
if(!((S >> (i - 1) & 1) && (S >> (j - 1) & 1))) continue;
if(fabs(a[i].x - a[j].x) < eps) continue;
double A,B;
equ(A,B,a[i].x,a[j].x,a[i].y,a[j].y);
if(A > -eps) continue;
s = (1 << (i - 1)) + (1 << (j - 1));
for(int k = 1; k <= n; k++)
{
if(k == i || k == j) continue;
if(fabs(A * a[k].x * a[k].x + B * a[k].x - a[k].y) < eps)
{
// printf("i = %lld,j = %lld, k = %lld\n",i,j,k);
s = (s + (1 << (k - 1)));
}
}
if(S - s >= 0) dp[S] = min(dp[S],dp[S - s] + 1);
}
}
}
printf("%lld\n",dp[(1 << n) - 1]);
}
return 0;
}