[JSOI2007]合金

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(int k=0;k<nVertex;++k){

    //新增部分:

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

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

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

    //通常的 floyd 部分:

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

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

            int temp = 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
   if a<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]);
   if s<-eps then 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:= true else right:= false ;
end ;
//============================================================================
begin
   readln(m,n);
   for i:= 1 to m do readln(dx[i],dy[i]);
   for i:= 1 to n do readln(x[i],y[i]);
   fillchar(p,sizeof(p), 0 );
   for i:= 1 to m do for j:= 1 to m do gg[i,j]:= 100000 ;
   for i:= 1 to m do
     for j:= 1 to m do
     begin
       flag:= true ;
       for k:= 1 to n 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 ;
       if flag then
       begin
         gg[i,j]:= 1 ; p[i]:= true ; p[j]:= true ;
         pp[i,j]:= true         //删掉没用的点。
       end ;
     end ;
   tot:= 0 ;
   for i:= 1 to m do
     if p[i] then
     begin
       inc(tot);
       a[i]:=tot;
     end ;
   for i:= 1 to tot do for j:= 1 to tot do g[i,j]:= 100000 ;
   for i:= 1 to m do
     for j:= 1 to m do
       if pp[i,j] then g[a[i],a[j]]:=gg[i,j];
   ans:= 100000 ;
   for k:= 1 to tot do        //Floyd
     for i:= 1 to tot do
       for j:= 1 to tot do
         g[i,j]:=min(g[i,j],g[i,k]+g[k,j]);
   for i:= 1 to tot do        //找最小环
     for j:= 1 to tot 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];
   if ans= 100000 then writeln ( '-1' ) else
   writeln (ans);
end .
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值