[洛谷P4049] JSOI2007 合金
问题描述
某公司加工一种由铁、铝、锡组成的合金。他们的工作很简单。首先进口一些铁铝锡合金原材料,不同种类的原材料中铁铝锡的比重不同。然后,将每种原材料取出一定量,经过融解、混合,得到新的合金。新的合金的铁铝锡比重为用户所需要的比重。
现在,用户给出了 \(n\) 种他们需要的合金,以及每种合金中铁铝锡的比重。公司希望能够订购最少种类的原材料,并且使用这些原材料可以加工出用户需要的所有种类的合金。
输入格式
第一行两个整数 \(m\) 和 \(n\),分别表示原材料种数和用户需要的合金种数。
第 \(2\) 到 \(m+1\) 行,每行三个实数 \(a_i, b_i, c_i\),分别表示铁铝锡在一种原材料中所占的比重。
第 \(m+2\) 到 \(m+n+1\) 行,每行三个实数 \(d_i, e_i, f_i\),分别表示铁铝锡在一种用户需要的合金中所占的比重。
输出格式
一个整数,表示最少需要的原材料种数。若无解,则输出 –1
。
样例输入
10 10
0.1 0.2 0.7
0.2 0.3 0.5
0.3 0.4 0.3
0.4 0.5 0.1
0.5 0.1 0.4
0.6 0.2 0.2
0.7 0.3 0
0.8 0.1 0.1
0.9 0.1 0
1 0 0
0.1 0.2 0.7
0.2 0.3 0.5
0.3 0.4 0.3
0.4 0.5 0.1
0.5 0.1 0.4
0.6 0.2 0.2
0.7 0.3 0
0.8 0.1 0.1
0.9 0.1 0
1 0 0
样例输出
5
数据范围
对于全部的测试点,满足 \(1\le m,n\le 500\),\(0 \leq a_i,b_i,c_i,d_i,e_i,f_i \leq 1\),且 \(a_i+b_i+c_i=1\),\(d_i+e_i+f_i=1\),小数点后最多有六位数字。
解析
发现只需要用前两个比重就能够表示一种合金。不妨将一种合金看做是一个向量,问题就变成最少能用多少种向量表示出所有的目标向量。
因为所需要的合金单位体积为1,由向量表示的定理:
若 \(\vec{c}\) 能够被 \(\vec{a}\) 和 \(\vec{b}\) 表示,则存在 \(x+y=1\) 满足 \(x\vec{a}+y\vec{b}=\vec{c}\)
即两个合金能够任意比例混合得到的合金对应的点一定在这两个合金对应的点的连线上。推广一下,三个合金能够任意比例混合得到的合金一定在这三个合金组成的三角形里面。因此我们只要找到由原材料组成的凸包能够包括所有目标向量即可。
我们两两枚举原材料,然后看做是一个有向线段(可能有两个方向)。假设凸包是逆时针的,如果一个目标合金与起点的连线的斜率小于这两个合金所在直线的斜率,说明这根有向线段不可能出现在凸包中。斜率相等时,这个目标合金就必须在线段上。
对于每一个可行的有向线段,我们都把他看做是有向边。枚举完所有有向线段后,我们跑一边图上最小环即可。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define int long long
#define N 502
using namespace std;
const double eps=1e-7;
int n,m,i,j,k,g[N][N],dis[N][N];
double a[N],b[N],c[N],d[N],e[N],f[N];
double cal(int x,int y,int z)
{
return (e[z]-b[x])*(a[y]-a[x])-(d[z]-a[x])*(b[y]-b[x]);
}
signed main()
{
scanf("%lld%lld",&m,&n);
for(i=1;i<=m;i++){
for(j=1;j<=m;j++) g[i][j]=dis[i][j]=1<<30;
}
for(i=1;i<=m;i++) scanf("%lf%lf%lf",&a[i],&b[i],&c[i]);
for(i=1;i<=n;i++) scanf("%lf%lf%lf",&d[i],&e[i],&f[i]);
for(i=1;i<=m;i++){
bool flag=1;
for(j=1;j<=n;j++){
if(fabs(a[i]-d[j])>eps||fabs(b[i]-e[j])>eps){
flag=0;
break;
}
}
if(flag){
puts("1");
return 0;
}
}
for(i=1;i<=m;i++){
for(j=1;j<=m;j++){
if(i==j||(fabs(a[i]-a[j])<eps&&fabs(b[i]-b[j])<eps)) continue;
bool flag=1;
for(k=1;k<=n;k++){
double tmp=cal(i,j,k);
if(tmp<-eps||(tmp>-eps&&tmp<eps&&((d[k]<a[i]&&d[k]<a[j])||(d[k]>a[i]&&d[k]>a[j])||(e[k]>b[i]&&e[k]>b[j])||(e[k]<b[i]&&e[k]<b[j])))){
flag=0;
break;
}
}
if(flag) g[i][j]=dis[i][j]=1;
}
}
int ans=1<<30;
for(k=1;k<=m;k++){
for(i=1;i<=m;i++){
for(j=1;j<=m;j++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
for(i=1;i<=m;i++){
for(j=1;j<=m;j++){
if(i==j) ans=min(ans,dis[i][i]);
else ans=min(ans,dis[i][j]+dis[j][i]);
}
}
if(ans==1<<30) puts("-1");
else printf("%lld\n",ans);
return 0;
}