Noip2016 憤怒的小鳥 - 狀壓DP
阿新 • • 發佈:2018-11-02
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]); } }