[NOIP2016] 愤怒的小鸟题解

愤怒的小鸟

题目来源

NOIP 2016 D2T3

题目描述

在一个二维平面上,有一只小鸟在   ( 0 ,   0 ) \ (0,\ 0)  (0, 0) 处,有   n \ n  n 只小猪在   ( x i ,   y i ) \ (x_i,\ y_i)  (xi, yi) 处。小鸟可以以   y = a x 2 + b x   ( a < 0 ) \ y=ax^2+bx\ (a<0)  y=ax2+bx (a<0) 的抛物线的飞出,并消灭在它飞行路径上的所有小猪。求小鸟至少要飞几次才可以消灭所有的小猪。包含多组测试数据。   n ≤ 18 ,   T ≤ 30 ,   x i , y i ∈ d o u b l e \ n\leq 18,\ T\leq 30,\ x_i,y_i\in double  n18, T30, xi,yidouble

解题方法
方法一 (朴素版)

还是状压DP,可以直接一维,定义   f s \ f_s  fs 表示消灭状态为   s \ s  s 时小鸟飞的最少次数。
如果直接暴力状压的话时间复杂度为   O ( T n 3 2 n ) \ O(Tn^32^n)  O(Tn32n) ,因为还要枚举   a ,   b \ a,\ b  a, b ,显然是不可以接受的。

方法二 (AC版)

推式子
假设现在有两点   ( x 1 ,   y 1 ) ,   ( x 2 ,   y 2 ) \ (x_1,\ y_1),\ (x_2,\ y_2)  (x1, y1), (x2, y2) ,那么代入   y = a x 2 + b x \ y=ax^2+bx  y=ax2+bx 实在是做不了,下面给出推导为   a ,   b \ a,\ b  a, b 的式子:
{ x 1 2 a + x 1 b = y 1 x 2 2 a + x 2 b = y 2 → { x 1 2 x 2 a + x 1 x 2 b = x 2 y 1 x 1 x 2 2 a + x 1 x 2 b = x 1 y 2 → a = x 2 y 1 − x 1 y 2 x 1 2 x 2 − x 1 x 2 2 → a = y 1 x 1 − y 2 x 2 x 1 − x 2 ,   b = y 1 x 1 − a x 1 \left\{\begin{matrix} x_1^2a+x_1b=y_1\\ x_2^2a+x_2b=y_2 \end{matrix}\right. \to \left\{\begin{matrix} x_1^2x_2a+x_1x_2b=x_2y_1\\ x_1x_2^2a+x_1x_2b=x_1y_2 \end{matrix}\right. \to a=\frac{x_2y_1-x_1y_2}{x_1^2x_2-x_1x_2^2} \to a=\frac{\frac{y_1}{x_1}-\frac{y_2}{x_2}}{x_1-x_2} ,\ b=\frac{y_1}{x_1}-ax_1 {x12a+x1b=y1x22a+x2b=y2{x12x2a+x1x2b=x2y1x1x22a+x1x2b=x1y2a=x12x2x1x22x2y1x1y2a=x1x2x1y1x2y2, b=x1y1ax1
但是,并不是所有的   x 1 ,   x 2 ,   y 1 ,   y 2 \ x_1,\ x_2,\ y_1,\ y_2  x1, x2, y1, y2 都可以形成二次函数,   x 1 ≠ x 2 \ x_1\neq x_2  x1=x2 才可以,因为没有两点在一条抛物线上绝对垂直。
题目要求   a < 0 \ a<0  a<0 ,要特判。

定义
和朴素做法一样,定义   f s \ f_s  fs 表示消灭状态为   s \ s  s 时最少需要发射的小鸟数。
但是,不同处在于,可以预处理   f l y i ,   j \ fly_{i,\ j}  flyi, j
定义   f l y i ,   j \ fly_{i,\ j}  flyi, j 为小猪   i \ i  i 和小猪   j \ j  j 能组成的抛物线所能经过的其它小猪,需要注意的是   f l y \ fly  fly 存的是一个状态。

举个例子:
如果   i ,   j \ i,\ j  i, j 的抛物线经过   k \ k  k ,则   f l y i ,   j = f l y i ,   j ∣ 2 k − 1 \ fly_{i,\ j}=fly_{i,\ j}|2^{k-1}  flyi, j=flyi, j2k1

状态转移
先给出循环框架:

for (int s = 0; s < (1 << n); s++)
	for (int i = 1; i <= n; i++)
		if (!(s & (1 << (i - 1)))) //抛物线上的点必须没有被消灭过
			for (int j = 1; j <= n; j++) {
				...
			}

然后要分情况讨论:

  1.   i = j \ i=j  i=j ,这种情况表示只消灭掉一只,可能是因为剩下的所有都有同一横坐标或者只有一只了。
      f s ∣ 2 i − 1 = m i n ( f s ∣ 2 i − 1 ,   f s + 1 ) \ f_{s|2^{i-1}}=min(f_{s|2^{i-1}},\ f_s+1)  fs2i1=min(fs2i1, fs+1)
    要么利用已经计算过的最小值,要么新开一条抛物线。
  2.   i ≠ j \ i\neq j  i=j ,这种情况表示消灭两只及以上。一条抛物线肯定能消灭两只,至于更多只就看造化了。
      f s ∣ f l y i ,   j = m i n ( f s ∣ f l y i ,   j ,   f s + 1 ) \ f_{s|fly_{i,\ j}}=min(f_{s|fly_{i,\ j}},\ f_s+1)  fsflyi, j=min(fsflyi, j, fs+1)

以下为核心代码:

for (int s = 0; s < (1 << n); s++)
	for (int i = 1; i <= n; i++)
		if (!(s & (1 << i - 1))) {
			for (int j = 1; j <= n; j++) {
				if (i == j) f[s | 1 << (i - 1)] = min(f[s | 1 << (i - 1)], f[s] + 1);
				if (fabs(x[i] - x[j]) < eps) continue;
				f[s | fly[i][j]] = min(f[s | fly[i][j]], f[s] + 1);
			}
			break; //剪枝,两点的先后顺序不影响结果
		}

这样的话,通过预处理,时间复杂度降至   O ( T ( n 3 + n 2 2 n ) ) \ O(T(n^3+n^22^n))  O(T(n3+n22n)) ,约等于   T n 2 2 n \ Tn^22^n  Tn22n ,但是容易被卡常,但是在   n 2 2 n \ n^22^n  n22n 中有许多状态在集合中,所以已经删掉了许多状态,总体时间可以接受。

代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
const double eps = 1e-7;
int T;
int f[1 << N];
int fly[N][N];
double x[N], y[N];
inline int read()
{
	register int sum = 0, w = 1; register char ch = getchar();
	while (ch < '0' or ch > '9') { if (ch == '-') w = -1; ch = getchar(); }
	while (ch >= '0' and ch <= '9') { sum = sum * 10 + ch - '0'; ch = getchar(); }
	return sum * w;
}
int main()
{
	T = read();
	while (T--) {
		memset(fly, 0, sizeof fly);
		memset(f, 0x3f, sizeof f);
		int n = read(), m = read();
		for (int i = 1; i <= n; i++)
			scanf("%lf%lf", &x[i], &y[i]);
		for (int i = 1; i < n; i++)
			for (int j = i + 1; j <= n; j++) {
				if (fabs(x[i] - x[j]) < eps) continue; //x1, x2坐标不能相同
				double a = (y[i] / x[i] - y[j] / x[j]) / (x[i] - x[j]);
				if (a >= 0) continue;
				double b = y[i] / x[i] - a * x[i];
				for (int k = 1; k <= n; k++)
					if (fabs(y[k] / x[k] - (a * x[k] + b)) < eps) fly[i][j] |= 1 << k - 1;
			}
		f[0] = 0;
		for (int s = 0; s < (1 << n); s++)
			for (int i = 1; i <= n; i++)
				if (!(s & (1 << i - 1))) {
					for (int j = 1; j <= n; j++) {
						if (i == j) f[s | 1 << (i - 1)] = min(f[s | 1 << (i - 1)], f[s] + 1);
						if (fabs(x[i] - x[j]) < eps) continue;
						f[s | fly[i][j]] = min(f[s | fly[i][j]], f[s] + 1);
					}
					break;
				}
		printf("%d\n", f[(1 << n) - 1]);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值