[Noip2016]憤怒的小鳥
阿新 • • 發佈:2020-08-15
[Noip2016]憤怒的小鳥
一.前言
這題的優化確實讓我學到了很多,不像某 \(Archer\) 的毒瘤優化題……題目連結
二.思路
首先看 n 知狀壓 DP,這個好心的拋物線……只有兩個未知量,也就是隻要兩個點就能知道射這兩個點的拋物線解析式。這裡預處理一下算出來的拋物線會穿過的小豬集合……
考慮轉移,射一次,可能是盯著某一個豬去的,也可能是盯著兩個豬(等效於射一群)。兩個就是上限了,(除非你有紅茶的箭術hhhh),本來就是轉移狀態列舉就好……但是有一個奇妙的優化……。
首先挑出一隻還沒死的豬,為了取得答案這隻豬是必死無疑的。就是說你現在不殺以後遲早得殺,並且代價都是1,那不如早殺早超生……也就如果以後的一次抉擇會殺了這豬,那麼我們將這兩個決策順序交換了,不影響答案。所以每次轉移必殺第一個還沒死的豬。這就是這個神奇的優化。
三.CODE
#include<iostream> #include<cstdio> #include<algorithm> #include<fstream> #include<cmath> #include<cstring> using namespace std; #define dl double int read(){ char ch=getchar(); int res=0,f=1; for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1; for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0'); return res*f; } inline void solve(dl &a,dl &b,dl x1,dl y1,dl x2,dl y2){ a=(y2-y1*x2/x1)/(x2*x2-x1*x2); b=(y1-a*x1*x1)/x1; } const double eps=1e-6; int t,n,m,maxn; double x[20],y[20]; int line[20][20],dp[(1<<18)+1]; inline int lg2(int x){ int cnt=-1; while(x)x>>=1,cnt++; return cnt; } inline int get(int x){//找出第一個沒死的豬,愛咋寫咋寫 return lg2(((x)|(x+1))-x); } int main(){ t=read(); while(t--){ n=read();m=read(); memset(line,0,sizeof(line)); memset(dp,127/3,sizeof(dp)); dp[0]=0;maxn=(1<<n); for(int i=0;i<n;++i)scanf("%lf%lf",&x[i],&y[i]); for(int i=0;i<n;++i){ for(int j=i+1;j<n;++j){ if(fabs(x[i]-x[j])<eps)continue;//無解 dl a,b; solve(a,b,x[i],y[i],x[j],y[j]);//計算解析式 if(a>-eps)continue; for(int k=0;k<n;++k) if(fabs(y[k]-a*x[k]*x[k]-b*x[k])<eps)line[i][j]|=(1<<k);//記錄集合 } } for(int i=0;i<maxn;++i){ int j=get(i); dp[i|(1<<j)]=min(dp[i|(1<<j)],dp[i]+1);//只射這一個 for(int k=j+1;k<n;++k)dp[i|line[j][k]]=min(dp[i|line[j][k]],dp[i]+1);//射一群 } printf("%d\n",dp[maxn-1]); } return 0; }