传送门
解析:
数据范围 18 18 18,多么显然的状压。。。
但是!!!为什么 O ( n 2 2 n ) O(n^22^n) O(n22n)能过???复杂度明显不对啊。。。
这里提供一种 O ( n 2 n ) O(n2^n) O(n2n)的做法(博主也是看了别人的题解学习的这种做法)
思路:
首先枚举所有合法的线(即枚举两只猪),预处理这条线能够穿过的猪。
然后直接大力
D
P
DP
DP就是
O
(
n
2
2
n
)
O(n^22^n)
O(n22n)。
但是可以优化到
O
(
n
2
n
)
O(n2^n)
O(n2n)。
关于做法这里不作赘述,只谈优化。
我们为什么要枚举
O
(
n
2
)
O(n^2)
O(n2)条线?
我们只需要枚举
O
(
n
)
O(n)
O(n)条线!
考虑集合当中编号最小的没有被考虑的猪 i i i,这可以预处理,可以用位运算(还是需要预处理,不过要简单的多)。这只猪在这次投射中必须被打中,不然我们在后面的状态中还要回过头来打它,这是多余的转移。
所以我们只枚举和这只猪有关的线就行了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
inline double getdb(){
re double x,y=1.0;
re char c;
while(!isdigit(c=gc()));x=c^48;
while(isdigit(c=gc()))x=(x*10)+(c^48);
if(c!='.')return x;
while(isdigit(c=gc()))x+=(y/=10)*(c^48);
return x;
}
cs double eps=1e-6;
inline int sign(cs double &x){
return fabs(x)<eps?0:(x>0?1:-1);
}
int lownbit[(1<<18)+3];
double x[20],y[20];
int line[20][20];
int dp[(1<<18)+3];
int T,n;
signed main(){
for(int re i=0;i<18;++i)lownbit[1<<i]=i;
scanf("%d",&T);
while(T--){
scanf("%d%*d",&n);
for(int re i=0;i<n;++i)x[i]=getdb(),y[i]=getdb();
memset(dp,0x3f,sizeof dp);
memset(line,0,sizeof line);
for(int re i=0;i<n;++i){
for(int re j=0;j<n;++j){
if(!sign(x[i]-x[j]))continue;
double y_1=y[i]/x[i],x_1=x[i];
double y_2=y[j]/x[j],x_2=x[j];
double a=(y_1-y_2)/(x_1-x_2),b=y_1-x_1*a;
if(a>-eps||b<0)continue;
for(int re k=0;k<n;++k)if(!sign(a*x[k]+b-y[k]/x[k]))line[i][j]|=1<<k;
}
}
dp[0]=0;
for(int re i=0;i+1<(1<<n);++i){
int j=lownbit[(~i)&(-(~i))];
dp[i|(1<<j)]=min(dp[i|(1<<j)],dp[i]+1);
for(int re k=0;k<n;++k)dp[i|line[j][k]]=min(dp[i|line[j][k]],dp[i]+1);
}
printf("%d\n",dp[(1<<n)-1]);
}
return 0;
}