[JSOI2007]合金

来源:互联网 发布:网络平台金融整顿 编辑:程序博客网 时间:2024/04/28 04:11

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

3 2
0.25 0.25 0.5
0 0.6 0.5
1 0 0
0.7 0.1 0.2
0.85 0.05 0.1

Sample Output

2
 
首先,对于每一个合金,其实我们可以只用其中的两个金属的比率,剩下的那个用一减就可以了。
然后,就可以在一个二维坐标中用点表示出所有提供的金属了。
这样,可以发现,多个点围成的凸包内部(包括边界)的任意点所表示的合金都是可以合成的了(可以从一条线证明起,再用三角形,至于多边形中,对于每个点都必然回落在边上三点所围成的三角形内,就简化成三边)。
所以,只要找到几个点围成一个凸包(至于为什么一定是凸包。。。很明显),包括所有需要的金属点,那么,最少点数的凸包的点数即为所求。注意,凸包有可能是一条直线,或只有一个点
那么,对于任意一条边,只要满足所有需要的金属都在他一侧,就有可能在结果凸包上。所以,对于所有这样的边都连一条权为一的边,然后跑一遍最小环即可。
 
最小环:
   对于有向图,跑一遍Floyd,然后枚举两点,ans:=min(g[i,j]+g[j,i])。
   对于无向图,在跑Floyd的同时,更新ans。
   具体做法为:

for(intk=0;k<nVertex;++k){

    //新增部分:

    for(inti=0;i<k;++i)

        for(intj=0;j<i;++j)

            mincircle= min(mincircle,Dist[i][j]+Graph[j][k]+Graph[k][i]);

    //通常的floyd 部分:

    for(inti=0;i<nVertex;++i)

        for(intj=0;j<i;++j){

            inttemp = Dist[i][k] + Disk[k][j];

            if(temp< Dist[i][j])

                Dist[i][j]= Dist[j][i] = temp;

        }

                    -------http://jiyuede.blog.163.com/blog/static/33251921200971184117557/

方法就是找到最小环上编号最大的点,最小环就是编号最大的点与其他两个点的连边再加上那两个点之间的最短路。Floyd最外层循环为k,即为枚举中间节点,在跑最短路之前,枚举i,j。

则:ans:=min(ans,dist[i][j]+graph[j][k]+graph[k][i])。

为什么呢?

Floyd要枚举中间节点,所以k不可能在i到j的最短路上,所以以上算法可以求得最小环

 

AC CODE

 

program hy_1027;
const eps=1e-7;
var g,gg,dist:array[1..500,1..500]of longint;
    pp:array[1..500,1..500]of boolean;
    dx,dy,x,y:array[1..500]of double;
    p:array[1..500]of boolean;
    a:array[1..500]of longint;
    tot,i,j,k,ans,n,m:longint;
    flag:boolean;
//============================================================================
function min(a,b:longint):longint;
begin
  ifa<b then min:=a else min:=b;
end;
//============================================================================
function right(g,h,k:longint):boolean   //判点是否在线的右边,用向量叉积的正负,同时要注var s:double                              意是否在线上。
begin
  s:=(dx[h]-dx[g])*(y[k]-dy[g])-(x[k]-dx[g])*(dy[h]-dy[g]);
  ifs<-epsthen right:=true else
  if(abs(s)<eps) and (abs(abs(dx[h]-x[k])+abs(x[k]-dx[g])-abs(dx[g]-dx[h]))<eps)then
  right:=trueelse right:=false;
end;
//============================================================================
begin
  readln(m,n);
  fori:=1 tom do readln(dx[i],dy[i]);
  fori:=1 ton do readln(x[i],y[i]);
  fillchar(p,sizeof(p),0);
  fori:=1 tom do forj:=1 tom do gg[i,j]:=100000;
  fori:=1 tom do
    forj:=1 tom do
    begin
      flag:=true;
      fork:=1 ton do
        if((i<>j)and not(right(i,j,k))) or ((i=j)and ((dx[i]<>x[k])or (dy[i]<>y[k])))then       //要判断只用一个提供的合金就可以的满足条件的情况。
        begin
          flag:=false;
          break;
        end;
      ifflag then
      begin
        gg[i,j]:=1;p[i]:=true; p[j]:=true;
        pp[i,j]:=true       //删掉没用的点。
      end;
    end;
  tot:=0;
  fori:=1 tom do
    ifp[i] then
    begin
      inc(tot);
      a[i]:=tot;
    end;
  fori:=1 totot do forj:=1 totot do g[i,j]:=100000;
  fori:=1 tom do
    forj:=1 tom do
      ifpp[i,j] then g[a[i],a[j]]:=gg[i,j];
  ans:=100000;
  fork:=1 totot do       //Floyd
    fori:=1 totot do
      forj:=1 totot do
        g[i,j]:=min(g[i,j],g[i,k]+g[k,j]);
  fori:=1 totot do       //找最小环
    forj:=1 totot do
      if(i<>j)and (g[i,j]+g[j,i]<ans)then ans:=g[i,j]+g[j,i] else
      if(i=j) and (g[i,j]<ans) then ans:=g[i,j];
  ifans=100000 then writeln('-1')else
  writeln(ans);
end.
0 0