一看就是道毒瘤题
两位数的数据范围,一看不是暴搜枚举就是状压了
我们考虑状压啦
预处理出
O
(
n
2
)
O(n^2)
O(n2)条抛物线各自串起来哪几只猪,然后位运算暴算一通
先推个抛物线公式
{
x
1
2
a
+
x
1
b
=
y
1
x
2
2
a
+
x
2
b
=
y
2
\begin{aligned}\begin{cases} {x_1}^2a+x_1b=y_1\\ {x_2}^2a+x_2b=y_2\\ \end{cases}\end{aligned}
{x12a+x1b=y1x22a+x2b=y2
一
顿
乱
推
之
后
…
…
一顿乱推之后……
一顿乱推之后……
{
a
=
y
1
−
x
1
y
2
x
2
x
1
2
−
x
1
x
2
b
=
y
1
x
1
−
x
1
2
a
\begin{aligned}\begin{cases} a=\dfrac{y_1-\dfrac{x_1y_2}{x_2}}{{x_1}^2-x_1x_2}\\\\ b=\dfrac{y_1}{x_1}-{x_1}^2a\\ \end{cases}\end{aligned}
⎩⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎧a=x12−x1x2y1−x2x1y2b=x1y1−x12a
然后挨个算每只猪能不能被串,可以就把这条抛物线一位改为1
还要注意抛物线的
a
a
a不能大于0,要不然你的鸟就上天了
然后就状压DP
#include<cstdio>
#include<cstring>
using namespace std;
const double eps=1e-8;
inline double abs(const double x)
{return x<0?-x:x;}
inline int min(const int a,const int b)
{return a<b?a:b;}
int f[1<<18];
struct point
{
double x,y;
}a[19];
int para[510],len;//para[i]:第i条抛物线可以串起来的猪的状压
void reset()
{
memset(para,0,sizeof(para));
len=0;
}
void Main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=0;i<1<<n;i++)//递推求popcount(二进制下1的个数)
f[i]=f[i>>1]+(i&1);//最多情况下就一只鸟一只猪呗
double A,B;//抛物线中的a和b
for(int i=1;i<=n;i++)
scanf("%lf%lf",&a[i].x,&a[i].y);
for(int i=1;i<=n;i++)
{
#define x1 a[i].x//为了让代码更好看
#define y1 a[i].y
#define x2 a[j].x
#define y2 a[j].y
for(int j=1;j<i;j++)
if(abs(x1-x2)>eps)//横坐标相同会除以零的
{
A=(y1-x1*y2/x2)/(x1*(x1-x2));
if(A>-eps)continue;//你鸟上天了
B=(y1-x1*x1*A)/x1;
len++;
#define x a[k].x
#define y a[k].y
for(int k=1;k<=n;k++)
if(abs(A*x*x+B*x-y)<=eps)
para[len]|=1<<k-1;//para
#undef x
#undef y
}
#undef x1
#undef y1
#undef x2
#undef y2
}
for(int i=0;i<1<<n;i++)
{
for(int j=1;j<=len;j++)
f[i|para[j]]=min(f[i|para[j]],f[i]+1);//多射一次或者用原来的方案
}
printf("%d\n",f[(1<<n)-1]);//把n只猪串起来
}
int main()
{
int T;scanf("%d",&T);
while(T--)
reset(),Main();
return 0;
}
当然,这不是最优的做法
我们很容易发现,para
数组中存的很多线都是重复的,一条有
n
n
n个点的线会出现
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1)次
例如:抛物线
{
i
,
j
,
k
}
\{i,j,k\}
{i,j,k}会以
{
i
,
j
}
,
{
i
,
k
}
,
{
j
,
k
}
\{i,j\},\{i,k\},\{j,k\}
{i,j},{i,k},{j,k}的形式出现三次
这就很浪费了
于是我们改进一下:
既然最后要将每个位都填满,那我们不如从左到右填吧?
于是预处理出每个数最低位的0,每次就选取经过最低位0的
O
(
n
)
O(n)
O(n)条抛物线进行转移。
这样复杂度就从
O
(
2
n
n
2
)
O(2^nn^2)
O(2nn2)降到了
O
(
2
n
n
)
O(2^nn)
O(2nn)
#include<cstdio>
#include<cstring>
using namespace std;
const double eps=1e-8;
inline double abs(const double x)
{return x<0?-x:x;}
inline int min(const int a,const int b)
{return a<b?a:b;}
int low0[1<<18];
void init()
{
for(int i=1;i<1<<18;i++)
low0[i]=i&1?low0[i>>1]+1:0;//递推求最低一个零
}
int f[1<<18];
struct point
{
double x,y;
}a[19];
int para[19][19],len;
void reset()
{
memset(para,0,sizeof(para));
len=0;
}
void Main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=0;i<1<<n;i++)
f[i]=f[i>>1]+(i&1);
double A,B;
for(int i=0;i<n;i++)
scanf("%lf%lf",&a[i].x,&a[i].y);
for(int i=0;i<n;i++)
{
#define x1 a[i].x
#define y1 a[i].y
#define x2 a[j].x
#define y2 a[j].y
for(int j=0;j<i;j++)
if(abs(x1-x2)>eps)
{
A=(y1-x1*y2/x2)/(x1*(x1-x2));
if(A>-eps)continue;
B=(y1-x1*x1*A)/x1;
#define x a[k].x
#define y a[k].y
for(int k=0;k<n;k++)
if(abs(A*x*x+B*x-y)<=eps)
para[j][i]|=1<<k;
#undef x
#undef y
}
#undef x1
#undef y1
#undef x2
#undef y2
}
for(int i=0;i<1<<n;i++)
{
int j=low0[i];//就是这里!一定要经过它!
for(int k=0;k<n;k++)
f[i|para[j][k]]=min(f[i|para[j][k]],f[i]+1);
}
printf("%d\n",f[(1<<n)-1]);
}
int main()
{
init();
int T;scanf("%d",&T);
while(T--)
reset(),Main();
return 0;
}