1. 程式人生 > 實用技巧 >[BZOJ1027][JSOI2007]合金(凸包+最短路)

[BZOJ1027][JSOI2007]合金(凸包+最短路)

[BZOJ1027][JSOI2007]合金(凸包+最短路)

題面

某公司加工一種由鐵、鋁、錫組成的合金。他們的工作很簡單。首先進口一些鐵鋁錫合金原材料,不同種類的原材料中鐵鋁錫的比重不同。然後,將每種原材料取出一定量,經過融解、混合,得到新的合金。新的合金的鐵鋁錫比重為使用者所需要的比重。

現在,使用者給出了n種他們需要的合金,以及每種合金中鐵鋁錫的比重。公司希望能夠訂購最少種類的原材料,並且使用這些原材料可以加工出使用者需要的所有種類的合金。

分析

容易發現\(c=1-a-b\),那麼就沒必要考慮\(c\),只考慮\(a,b\). 我們先考慮2種合金\((a_i,b_i)(a_j,b_j)\)混合的情況,設比例分別為\(\lambda,\mu(\lambda+\mu=1)\)

,那得到的新合金就是\((\lambda a_i+\mu a_j,\lambda b_i+\mu b_j)\).把\((a,b)\)看成平面直角座標系上的一個點\(A_i\),設原點為\(O\).那麼得到的新合金為\(\lambda \vec{OA_i}+\mu \vec{OA_j}\). 由於\(\lambda+\mu=1\)根據向量知識可以得到,新的合金在點\(A_i,A_j\)的連線上。

再考慮3種的情況.如上圖,在兩兩相加形成的線段上任取兩個點,新的合金在兩點連線上。無數多的線段疊加在一起,就得到了一個三角形區域。以此類推,\(n\)種合金能夠合成的金屬對應的點在包含這\(n\)個點的多邊形內

那麼問題變成了,選擇最少的點,使得這些點構成的凸包內部含有所有需要的點。我們可以列舉可能成為凸包的線段\(A_iA_j\),從\(i\)\(j\)連一條邊權為1的邊。判斷是否可能在凸包上,只需要用叉積判斷是否存在點在向量\(\vec{A_iA_j}\)右側即可,注意特判共線但不線上段上的情況。這一步的複雜度是\(O(n^2m)\).這樣圖上一個迴路就對應一個凸包,Floyd求最小環即可。

程式碼

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<cmath>
#define maxn 500
#define eps 1e-7
#define INF 0x3f3f3f3f
using namespace std;
typedef double db;
struct Vector{
	db x;
	db y;
	Vector(){
		
	}
	Vector(db _x,db _y){
		x=_x;
		y=_y;
	}
	friend Vector operator + (Vector p,Vector q){
		return Vector(p.x+q.x,p.y+q.y);
	}
	friend Vector operator - (Vector p,Vector q){
		return Vector(p.x-q.x,p.y-q.y); 
	}
};
db cross(Vector p,Vector q){
	return p.x*q.y-p.y*q.x;
}
db dot(Vector p,Vector q){
	return p.x*q.x+p.y*q.y;
}
inline int sgn(double x){
	if(fabs(x)<=eps) return 0;
	else if(x>0) return 1;
	else return -1; 
}

int n,m;
Vector a[maxn+5],b[maxn+5];
int dist[maxn+5][maxn+5]; 
bool check(int p,int q){
	for(int i=1;i<=m;i++){
		int f=sgn(cross(a[q]-a[p],b[i]-a[p]));
		if(f<0) return 0;//如果有點在PQ右側,說明一定不是凸包的邊
		if(f==0&&sgn(dot(b[i]-a[p],b[i]-a[q]))>0) return 0;//如果共線但不線上段PQ上 
	}
	return 1;
}
int floyd(){//找出包含所有b[i]的邊數最少的凸包,因為有向圖,直接輸出dist[i][i] 
	int ans=INF;
	for(int k=1;k<=n;k++){ 
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++) dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
		}
	} 
	for(int i=1;i<=n;i++) ans=min(ans,dist[i][i]);
	return ans;
}
int main(){
	db u,v,w;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lf %lf %lf",&u,&v,&w);
		a[i]=Vector(u,v);
	}	
	for(int i=1;i<=m;i++){
		scanf("%lf %lf %lf",&u,&v,&w);
		b[i]=Vector(u,v);
	}
	memset(dist,0x3f,sizeof(dist));
	for(int i=1;i<=n;i++){
//		dist[i][i]=0;
		for(int j=1;j<=n;j++){
			//看看i->j這條邊是否可能成為凸包的邊 
			if(check(i,j)){
//				printf("%d->%d\n",i,j);
				dist[i][j]=1;
			}
		}
	}
//	memcpy(dist,edge,sizeof(edge));
	int ans=floyd();
	if(ans>n) ans=-1;
	printf("%d\n",ans);	
}