1027: [JSOI2007]合金

引用:http://blog.csdn.net/popoqqq/article/details/40539273
Time Limit: 4 Sec Memory Limit: 162 MB
Submit: 3510 Solved: 996
[Submit][Status][Discuss]
Description
  某公司加工一种由铁、铝、锡组成的合金。他们的工作很简单。
  首先进口一些铁铝锡合金原材料,不同种类的原材料中铁铝锡的比重不同。
  然后,将每种原材料取出一定量,经过融解、混合,得到新的合金。新的合金的铁铝锡比重为用户所需要的比重。
  现在,用户给出了n种他们需要的合金,以及每种合金中铁铝锡的比重。
  公司希望能够订购最少种类的原材料,并且使用这些原材料可以加工出用户需要的所有种类的合金。
Input
  第一行两个整数m和n(m, n ≤ 500),分别表示原材料种数和用户需要的合金种数。第2到m + 1行,每行三
个实数a, b, c(a, b, c ≥ 0 且 a + b + c = 1),分别表示铁铝锡在一种原材料中所占的比重。第m + 2到m + n + 1行,每行三个实数a, b, c(a, b, c ≥ 0 且 a + b + c =1),分别表示铁铝锡在一种用户需要的合金中所占的比重。
Output
  一个整数,表示最少需要的原材料种数。若无解,则输出–1。
Sample Input
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
Sample Output
5
题目大意:
给定一些合金(每种合金里面都有3种成份),选择最少的合金,使这些合金可以按比例合成要求的全部合金。

首先由于x+y+z=1(xyz对应题目中abc) 所以我们只要得到x和y即可 z=1-x-y 所以z可以不读入了
所以描述每种合金只需要记录x和y。

然后我们把每种合金抽象成一个点(x,y)。可知两个点能合成的合金一定在两点连线的线段上。
证明:设两个点为(x1,y1)和(x2,y2),新合成的合金为(设(x1,y1)和(x2,y2)的比例为a:b),所以新合金的坐标为:( ax1+bx2 , ay2+by2 ) (a+b=1,a,b>0) 两点连线为(y-y1)/(x-x1)=(y-y2)/(x-x2),代入即可得证。
那么我们选定一些原料,这些原料能合成的合金一定在这些点所在的凸包上 证明略

于是我们就把问题转化成了这样:给定两个点集A和B,求A中最小的一个子集S,使B中所有的点在S的凸包内部。枚举A点集两点i,j(i可以等于j)若B点集中的所有点都在向量i->j的左侧或线段ij上,就连接一条i->j的单向边,然后Floyd求最小环即可。样例不错,方便调试(A点集和B点集一样)

#include<cstdio>
#include<cstring>
#include<cmath>
#define eps 1e-8
using namespace std;
struct node
{
    double x,y,z;
}a[510],b[510];
double multi(node p1,node p2,node p0)
{
    double x1=p1.x-p0.x;
    double y1=p1.y-p0.y;
    double x2=p2.x-p0.x;
    double y2=p2.y-p0.y;
    return x1*y2-x2*y1;
}
double dis(node p1,node p2)
{
    return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}
int d[510][510];
int main()
{
    int n,m;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++) scanf("%lf%lf%lf",&a[i].x,&a[i].y,&a[i].z);
    for(int i=1;i<=n;i++) scanf("%lf%lf%lf",&b[i].x,&b[i].y,&b[i].z);
    int i,j,k;double t;
    memset(d,63,sizeof(d));
    for(i=1;i<=m;i++)
        for(j=1;j<=m;j++)
        {
            for(k=1;k<=n;k++)
            {
                t=multi(a[j],b[k],a[i]);
                if(t<-eps) break;
                if( fabs(t)<eps && dis(a[i],b[k])>dis(a[i],a[j]) )break;
            }
            if(k==n+1) d[i][j]=1;
        }
    for(k=1;k<=m;k++)
        for(i=1;i<=m;i++)if(i!=k)
            for(j=1;j<=m;j++)if(j!=k)
                if( d[i][j]>d[i][k]+d[k][j] )d[i][j]=d[i][k]+d[k][j];
    int  ans=m+1;
    for(i=1;i<=m;i++) if(ans>d[i][i])ans=d[i][i];
    if(ans==m+1)printf("-1\n");else printf("%d\n",ans);

    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值