解题思路:
由于a+b+c=1,所以确定了a,b,那么c就被确定了,所以c这一维可以无视。
把一种合金(a,b)看做平面上的一个点。
那对于两个点A(a1,b1),B(a2,b2),那么线段AB就是它们可组成的合金,因为线段AB上的每个点都可表示为(xa1+(1-x)a2,xb1+(1-x)b2)(可以用向量证明)。
如果再加入一个点C,那线段AB上任意一点到C的线段上所表示的合金都可炼成,即三角形ABC中任意一点。
加以推广可得几个点所成凸包即为它们能炼成的合金。
于是我们就把问题转化成了这样:给定两个点集A和B,求A中最小的一个子集S,使B中所有的点在S的凸包内部
这个问题怎么处理呢?这里用到一个十分巧妙的方法。
枚举A点集两点i,j(i可以等于j)若B点集中的所有点都在向量i->j的左侧或线段ij上,就连接一条i->j的单向边。
即 若任意B点集中的点k满足(k->i)×(k->j)>0||(k->i)×(k->j)==0&&(k->i)·(k->j)<=0 则连接一条i->j的单向边。
然后Floyd求最小环即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<vector>
#include<queue>
#define ll long long
using namespace std;
int getint()
{
int i=0,f=1;char c;
for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
if(c=='-')c=getchar(),f=-1;
for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
return i*f;
}
const int N=505,INF=0x3f3f3f3f;
const double eps=1e-8;
int n,m,ans;
int f[N][N];
struct point
{
double x,y;
point(){}
point(double _x,double _y):
x(_x),y(_y){}
friend inline point operator -(const point &a,const point &b)
{return point(a.x-b.x,a.y-b.y);}
friend inline double operator *(const point &a,const point &b)
{return a.x*b.y-a.y*b.x;}
friend inline double operator ^(const point &a,const point &b)
{return a.x*b.x+a.y*b.y;}
}a[N],b[N];
void floyd()
{
for(int k=1;k<=m;k++)
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
for(int i=1;i<=m;i++)
ans=min(ans,f[i][i]);
}
int main()
{
//freopen("lx.in","r",stdin);
//freopen("lx.out","w",stdout);
memset(f,INF,sizeof(f));
int c;
m=getint(),n=getint();
for(int i=1;i<=m;i++)
scanf("%lf%lf%lf",&a[i].x,&a[i].y,&c);
for(int i=1;i<=n;i++)
scanf("%lf%lf%lf",&b[i].x,&b[i].y,&c);
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
{
int k;
for(k=1;k<=n;k++)
{
double delta=(a[i]-b[k])*(a[j]-b[k]);
if(delta<-eps)break;
if(abs(delta)<eps&&((a[i]-b[k])^(a[j]-b[k]))>eps)
break;
}
if(k==n+1)f[i][j]=1;
}
ans=INF;
floyd();
ans==INF?puts("-1"):printf("%d",ans);
return 0;
}