1. 程式人生 > >Noip2016 憤怒的小鳥 - 狀壓DP

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]); 
    }
}