1. 程式人生 > 實用技巧 >luogu P2831 [NOIP2016 提高組] 憤怒的小鳥

luogu P2831 [NOIP2016 提高組] 憤怒的小鳥

題面傳送門
資料這麼小顯然狀壓dp
考慮狀壓\(dp_i\)表示打掉的集合為\(i\)時最少拋物線條數。
預處理\(f_{i,j}\)表示過\(i\)點和\(j\)點的拋物線經過的點的集合。
但是這樣的轉移是\(O(T2^nn^2)\)的。
考慮怎麼優化。
很明顯我們的\(dp\)轉移是無序的。將其變成有序即先打最小的點則可優化掉一個\(n\)變為\(O(T2^nn)\)
程式碼實現:

#include<cstdio>
#include<cstring>
#define abs(x) ((x)>0?(x):-(x))
#define min(a,b) ((a)<(b)?(a):(b))
#define eps 1e-6
using namespace std;
int n,m,dp[1000039],g[1000039],t,a,b,f[39][39];
double x[39],y[39],nowx,nowy,x1,x2,y1,y2;
int main(){
//	freopen("1.in","r",stdin);
	register int i,j,k;
	scanf("%d",&t);
	for(i=0;i<(1<<18);i++){
		for(j=1;j<=18;j++) if(!(i&(1<<j-1))){g[i]=j;break;}
	}
	while(t--){
		scanf("%d%d",&n,&m);memset(dp,0x3f,sizeof(dp));dp[0]=0;
		for(i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]),dp[(1<<i-1)]=1;
		for(i=1;i<=n;i++){
			for(j=1;j<=n;j++){
				f[i][j]=0;
				x1=x[i];x2=x[j];y1=y[i];y2=y[j];
				if(abs(x1-x2)<=eps) continue;
				nowx=(y1*x2-y2*x1)/(x1*x1*x2-x2*x2*x1);
				nowy=(y1-nowx*x1*x1)/x1;
				if(nowx>0) continue;
				for(k=1;k<=n;k++) 
				if(abs(nowx*x[k]*x[k]+nowy*x[k]-y[k])<=eps) f[i][j]|=1<<k-1;
			}
		}
		for(i=0;i<(1<<n)-1;i++){
			for(j=1;j<=n;j++) dp[i|f[g[i]][j]]=min(dp[i|f[g[i]][j]],dp[i]+1);
			for(j=1;j<=n;j++) dp[i|(1<<j-1)]=min(dp[i|(1<<j-1)],dp[i]+1);
		}
		printf("%d\n",dp[(1<<n)-1]);
	}
}