Noip2016 愤怒的小鸟 - 状压DP

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

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


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

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

状态转移方程为

\[f(S|P[i])=min(f(S|P[i]),f(S)+1) \]

通过枚举两个点来确定一个抛物线,然后再去一个个检查还有哪些小猪是位于这条抛物线上的,这样确定好一条抛物线能够消灭的小猪集合,和目前的状态取并集,最后就能推出全集的答案
然后需要注意的就是精度问题了...
题目的精度只有两位,而显然都是小数的话,是他们没法出那种正好带入方程
\(y-ax^2-bx\) 答案就是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]); 
	}
}
posted @ 2018-11-02 14:14  Zolrk  阅读(93)  评论(0编辑  收藏  举报