Noip2016 愤怒的小鸟 【状压DP】

49 篇文章 0 订阅
6 篇文章 0 订阅

f(s)表示消灭s集合的小猪所需要的最小数量,考虑到n很小,最大才18,,用二进制来表示状态,0为第i个只小猪没有被消灭,1为第i只小猪被消灭了,就像010001010101这样的,最后求的就是111111111111111这样状态的值

P[i]表示所有可能的抛物线
需要注意的是,每条抛物线能够干掉的小猪数分为两种情况


  • 抛物线干掉了1只小猪
  • 抛物线干掉了2只或以上小猪

因为题目中说了只能发射 a < 0的抛物线,因此有时候我们通过两只小猪确定的抛物线不能收入备选集合,如果说不保留干掉1只小猪的抛物线,最后有可能不能通过备选集合干掉所有小猪,所以在枚举时我们不能把只含有一只小猪的抛物线覆盖掉

状态转移方程为
f ( S ∣ P [ i ] ) = m i n ( f ( S ∣ P [ i ] ) , f ( S ) + 1 ) f(S|P[i])=min(f(S|P[i]),f(S)+1) f(SP[i])=min(f(SP[i]),f(S)+1)

通过枚举两个点来确定一个抛物线,然后再去一个个检查还有哪些小猪是位于这条抛物线上的,这样确定好一条抛物线能够消灭的小猪集合,和目前的状态取并集,最后就能推出全集的答案
然后需要注意的就是精度问题了…
题目的精度只有两位,而显然都是小数的话,是他们没法出那种正好带入方程
y − a x 2 − b x y-ax^2-bx yax2bx 答案就是0的点,这时候可以定义一个比较小的数,Eps(也不能太小) 当算出的数比这个数还要小的时候,就可以认为这个数是0。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define DEBUG(x) std::cerr<<#x<<"="<<x<<std::endl;
const int maxp = 1 << 18 + 1;
const int maxn = 19;
const double Eps = 1e-6; 
int n,t,f[maxp],p[19*19],size,m;
struct Point{
	double x, y;
}point[maxn];
int main() {
	scanf("%d", &t);
	while(t--) {
		memset(p, 0, sizeof(p)), size = 0;
		memset(f, 0x3f, sizeof(f));
		f[0] = 0;//边界 
		scanf("%d %d",&n, &m);
		for(int i=1; i<=n; i++) {
			scanf("%lf %lf", &point[i].x, &point[i].y);
		}
		for(int i=1; i<=n; i++) {
			p[++size] = (1 << (i-1));//这里保留了只消灭一只小猪的抛物线 
			for(int j=i+1; j<=n; j++) {
				double x1 = point[i].x, y1 = point[i].y, x2 = point[j].x, y2 = point[j].y;
				double a = (x1 * y2 - x2 * y1) / (x1 * x2 * (x2 - x1));
				double b = (y1 * x2 * x2 - y2 * x1 * x1) / (x1 * x2 * (x2 - x1));
				if(a > -Eps) continue; //抛物线的开口要向下...
				size++;
				for(int k=1; k<=n; k++) {
					double kx = point[k].x, ky = point[k].y;
					if(fabs(ky-(a*kx*kx+b*kx)) <= Eps) p[size] |= (1<<(k-1));
				}
			}
		}
		for(int s=0; s<(1<<n); s++) {
			for(int i=1; i<=size; i++) {
				f[s|p[i]] = std::min(f[s|p[i]], f[s]+1);
			}
		}
		printf("%d\n",f[(1<<n)-1]); 
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值