2018.11.02 洛谷P2831 愤怒的小鸟(状压dp)

时间:2021-12-12 16:25:50

传送门

状压一眼题。


直接f[i]f[i]f[i]表示未选择状态为iii时的最小次数。

然后考虑现在怎么转移。

显然可以直接枚举消掉某一个点或者某两个点,复杂度O(n22n)O(n^22^n)O(n22n)

由于这个集合里面的所有点最终都会被消掉,因此顺序并不重要。

于是可以强制这一步会消掉lowbit(i)lowbit(i)lowbit(i)

那么当前有两个选择:

  1. 只消掉lowbit(i)lowbit(i)lowbit(i)
  2. 还会消掉其它的。

第一种直接递归,第二种可以预处理数组f[i][j]f[i][j]f[i][j]表示如果构造一条过了i,ji,ji,j的抛物线并消去上面的点,剩下的点的集合。

然后转移就很轻松了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=20;
int T,TT,n,stat[N][N],f[1<<18],bit[19],mp[(1<<18)+1];
bool g[N][N];
struct Node{double x,y;}p[N];
inline bool check(int pos,double a,double b){return fabs(a*p[pos].x*p[pos].x+b*p[pos].x-p[pos].y)>1e-10;}
inline int lowbit(int x){return x&-x;}
inline int countbit(int x){int ret=0;while(x)x-=lowbit(x),++ret;return ret;}
inline int dfs(int sta){
	if(~f[sta])return f[sta];
	if(!sta)return f[sta]=0;
	if(sta==lowbit(sta))return f[sta]=1;
	f[sta]=countbit(sta);
	int i=lowbit(sta),staa=sta-lowbit(sta);
	f[sta]=min(f[sta],1+dfs(sta^i));
	while(staa){
		int j=lowbit(staa);
		if(!g[mp[i]+1][mp[j]+1])f[sta]=min(f[sta],1+dfs(sta&stat[mp[i]+1][mp[j]+1]));
		staa-=j;
	}
	return f[sta];
}
int main(){
	scanf("%d",&T),bit[0]=1,mp[1]=0;
	for(int i=1;i<=18;++i)bit[i]=bit[i-1]<<1,mp[bit[i]]=i;
	while(T--){
		memset(f,-1,sizeof(f)),memset(g,0,sizeof(g)),scanf("%d",&n),scanf("%d",&TT);
		for(int i=1;i<=n;++i)scanf("%lf%lf",&p[i].x,&p[i].y);
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j){
				if(i==j){g[i][j]=1;continue;}
				int sta=0;
				double a1=p[i].x*p[i].x,b1=p[i].x,c1=-p[i].y,a2=p[j].x*p[j].x,b2=p[j].x,c2=-p[j].y;
				double a,b;
				if(fabs(b1-b2*a1/a2)>1e-5)b=-(c1-c2*a1/a2)/(b1-b2*a1/a2),a=(-c1-b1*b)/a1;
				else{g[i][j]=1;continue;}
				if(a>-1e-5){g[i][j]=1;continue;}
				else for(int k=1;k<=n;++k)if(check(k,a,b))sta|=1<<(k-1);
				stat[i][j]=sta;
			}
		printf("%d\n",dfs((1<<n)-1));
	}
	return 0;
}