1. 程式人生 > 實用技巧 >[洛谷P4049] JSOI2007 合金

[洛谷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;
}