1. 程式人生 > >P2831 憤怒的小鳥(狀壓dp)

P2831 憤怒的小鳥(狀壓dp)

P2831 憤怒的小鳥

我們先預處理出每個豬兩兩之間(設為$u,v$)和原點三點確定的拋物線(當兩隻豬橫座標相等時顯然無解)

處理出$u,v$確定的拋物線一共可以經過多少點,記為$lines[u][v]$

設$f[i]$表示已經被消滅的豬的集合為二進位制表示為$i$時,需要的最小拋物線數

顯然$f[0]=0$

$f[i|(1<<(u-1))]=min(f[i|(1<<(u-1)],f[i]+1)$(一條拋物線只串一個點)

$f[i|lines[u][v]]=min(f[i|lines[u][v]],f[i]+1)$

然鵝這是$O(Tn^{2}2^{n})$,ccf的老爺機會T

那麼我們考慮優化

我們發現加上拋物線時,先串$1,4$與先串$2,3$沒有區別,但是我們兩種都用不同的順序枚舉了一遍。

那麼我們可以處理出集合$i$的$mex$(在集合中沒有出現的最小正整數)

列舉時加上限制條件:一定要包含$mex[i]$

於是列舉就從$O(n^{2})$降到了$O(n)$

總複雜度就降到了$O(Tn2^{n})$

end.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define re register
using
namespace std; typedef double db; int min(int &a,int &b){return a<b?a:b;} #define N 524589 const db eps=1e-8; db x[25],y[25]; int t,n,m,lines[25][25],f[N],mex[N]; void calc(db &a,db &b,db x1,db y1,db x2,db y2){ b=(x1*x1*y2-x2*x2*y1)/(x1*x1*x2-x1*x2*x2); a=(y1-x1*b)/(x1*x1); }//用於解一元二次方程組
int main(){ for(re int i=0,j;i<262144;++i){//預處理mex for(j=0;(i&(1<<j))&&j<18;++j); mex[i]=j; }scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); db a,b; for(re int i=1;i<=n;++i) scanf("%lf%lf",&x[i],&y[i]); for(re int i=1;i<=n;++i) for(re int j=1;j<=n;++j){ lines[i][j]=0; if(fabs(x[i]-x[j])<eps) continue;//橫座標相等無解 calc(a,b,x[i],y[i],x[j],y[j]); if(a>-eps) continue; for(re int u=1;u<=n;++u) if(fabs(a*x[u]*x[u]+b*x[u]-y[u])<eps) lines[i][j]|=(1<<(u-1));//找到這條拋物線能連到的所有點 } memset(f,63,sizeof(f)); f[0]=0; for(re int i=0,j=mex[i];i<(1<<n);j=mex[++i]){ f[i|(1<<j)]=min(f[i|(1<<j)],f[i]+1); //單個點用掉一條的情況 for(re int u=1;u<=n;++u)//列舉的拋物線必須穿過j f[i|lines[j+1][u]]=min(f[i|lines[j+1][u]],f[i]+1); } printf("%d\n",f[(1<<n)-1]); }return 0; }