Noip2016 愤怒的小鸟 【状压DP】

来源:互联网 发布:mac 远程桌面 windows 编辑:程序博客网 时间:2024/05/16 12:32

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

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

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

这是两种状态,一个也不能少,不然转移就会无法考察到所有可能的情况,从而推出错误的答案。所以在枚举时我们不能把只含有一只小猪的抛物线覆盖掉,具体看代码。

状态转移方程为

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

通过枚举两个点来确定一个抛物线,然后再去一个个检查还有哪些小猪是位于这条抛物线上的,这样确定好一条抛物线能够消灭的小猪集合,和目前的状态取并集,最后就能推出全集的答案
然后需要注意的就是精度问题了…
题目的精度只有两位,而显然都是小数的话,是他们没法出那种正好带入方程
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]);     }}