bzoj1027 合金 Floyd求最小环 + 计算几何

1027: [JSOI2007]合金

Time Limit: 4 Sec   Memory Limit: 162 MB
Submit: 3891   Solved: 1143
[ 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

HINT

Source

思路:对于每种原料(a , b , c),因为有a + b + c = 1,所以知道了a、b之后c就知道了,所以用一个二维的(a, b)就可以表示一种原料,而这也就对应了二维平面上的一个点。同理,每一种待合成材料也可以用一个二维平面上的点来表示。

      那么,原料(x1 , y1)和原料(x2 , y2)可以合成的合金有哪些呢?这两者合成的合金对应的点如果是(x,y),那么有x = k * x1 + (1 - k) * x2 , y = k * y1 + (1 - k) * y2,直线的两点式为(x - x1) / (x2 - x1) = (y - y1) / (y2 - y1),我们发现(x,y)满足直线的两点式,所以(x,y)在过(x1 , y1),(x2 , y2)的直线上,又因为0<= k <= 1,所以(x,y)在以(x1 , y1)(x2 , y2)为端点的线段上。

     如果平面上代表原料的某些点构成了一个凸多边形,那么有这个凸多边形内部的每一个点代表的合金,都可以由构成凸多边形的原料来合成。怎么说明呢?首先,我们可以知道凸多边形的边上的每一点都可以合成(上一段已经说明),那么,对于凸多边形内部的任一点K,过它做一条直线,可以交凸多边形于两点P、Q,因为这两点P、Q都可以合成得到,所以K可以通过合成P、Q合成得到,所以K可以合成,所以凸多边形内部任一点都可以合成得到。

    所以问题就变成了这样,给定点集S1、S2,要求在S1中找一个点数最少的点集S‘,使得S’构成凸包可以包含S2的所有的点。

    对于这个问题,可以看成求最小环。我们这样建图:对于任意两点A、B,如果待合成的所有合金对应的点全部在直线AB的右边,或者在线段AB(注意是线段)上,那么,就建一条从点A到点B的有向边,然后在图上跑一次floyd求出最小环。注意这样求出的环的最小长度为2,但是有一种特殊情况,就是所有待合成合金重合于一点,然后这一点在原材料中就存在,那么在这种情况下答案就是1。

#pragma warning(disable:4786)
#pragma comment(linker, "/STACK:102400000,102400000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<cmath>
#include<string>
#include<sstream>
#include<bitset>
#define LL long long
#define FOR(i,f_start,f_end) for(int i=f_start;i<=f_end;++i)
#define mem(a,x) memset(a,x,sizeof(a))
#define lson l,m,x<<1
#define rson m+1,r,x<<1|1
using namespace std;
const int INF = 99999;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps=1e-10;
const int maxn = 500 + 5;
int dist[maxn][maxn] , mp[maxn][maxn];
int n , m , minloop;
int dcmp(double x)
{
    if(fabs(x) < eps)       return 0;
    else    return x > 0 ? 1 : -1;
}
struct Vector
{
    double x , y;
    Vector(double _x = 0 , double _y = 0) :x(_x),y(_y){};
}need[maxn] , has[maxn];
double Cross(const Vector & a , const Vector & b)
{
    return a.x * b.y - a.y * b.x;
}
double Dot(const Vector & a , const Vector & b)
{
    return a.x * b.x + a.y * b.y;
}
bool OnSegment(Vector p , Vector a1 , Vector a2)
{
    Vector v1 = Vector(a1.x - p.x , a1.y - p.y) , v2 = Vector(a2.x - p.x , a2.y - p.y);
    return dcmp(Cross(v1 , v2)) == 0 && dcmp(Dot(v1 , v2)) <= 0;
}
void create_edge(int x , int y)
{
    Vector v1 = Vector(has[y].x - has[x].x , has[y].y - has[x].y) , v2;
    for(int i = 1 ; i <= n ; i++){
        v2 = Vector(need[i].x - has[x].x , need[i].y - has[x].y);
        int t = dcmp(Cross(v1 , v2));
        if(t < 0)       return ;
        if(t == 0 && !OnSegment(need[i] , has[x] , has[y]))     return ;
    }
    mp[x][y] = dist[x][y] = 1;
}
void Floyd()      //有向图求最小环,其实就是做一次Floyd,然后找最小的dist[i][i]
{
    minloop = INF;
    for(int k = 1 ; k <= m ; k++){
        for(int i = 1 ; i <= m ; i++){
            for(int j = 1 ; j <=m ; j++){
                if(dist[i][j] > dist[i][k] + dist[k][j]){
                    dist[i][j] = dist[i][k] + dist[k][j];
                }
            }
        }
    }
    for(int i = 1 ; i <= m ; i++){
        minloop = min(minloop , dist[i][i]);
    }
}
int spj()
{
    int ret , flag = 1;
    for(int i = 2 ; i <= n ; i++){
        if(need[i].x != need[i - 1].x || need[i].y != need[i - 1].y){
            flag = 0;
            break;
        }
    }
    if(!flag)       return 0;
    for(int i = 1 ; i <= m ; i++){
        if(has[i].x == need[1].x && has[i].y == need[1].y){
            puts("1");      return 1;
        }
    }
    return 0;
}
int main()
{
    while(scanf("%d %d" , &m , &n) != EOF){
        double cc ;
        for(int i = 1 ; i <= m ; i++){
            scanf("%lf %lf %lf" , &has[i].x , &has[i].y , &cc);
        }
        for(int i = 1 ; i <= n ; i++){
            scanf("%lf %lf %lf" , &need[i].x , &need[i].y , &cc);
        }
        if(spj())       continue;
        for(int i = 1 ; i <= m ; i++){
            for(int j = 1 ; j <= m ; j++){
                mp[i][j] = dist[i][j] = INF;
            }
        }
        for(int i = 1 ; i <= m ;i++){
            for(int j = 1 ; j <= m ; j++){
                if(i != j)
                    create_edge(i , j);
            }
        }
        Floyd();
        if(minloop < INF)       printf("%d\n" , minloop);
        else                            printf("-1\n");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值