[BZOJ1027][JSOI2007]合金(凸包+最短路)
阿新 • • 發佈:2020-08-27
[BZOJ1027][JSOI2007]合金(凸包+最短路)
題面
某公司加工一種由鐵、鋁、錫組成的合金。他們的工作很簡單。首先進口一些鐵鋁錫合金原材料,不同種類的原材料中鐵鋁錫的比重不同。然後,將每種原材料取出一定量,經過融解、混合,得到新的合金。新的合金的鐵鋁錫比重為使用者所需要的比重。
現在,使用者給出了n種他們需要的合金,以及每種合金中鐵鋁錫的比重。公司希望能夠訂購最少種類的原材料,並且使用這些原材料可以加工出使用者需要的所有種類的合金。
分析
容易發現\(c=1-a-b\),那麼就沒必要考慮\(c\),只考慮\(a,b\). 我們先考慮2種合金\((a_i,b_i)(a_j,b_j)\)混合的情況,設比例分別為\(\lambda,\mu(\lambda+\mu=1)\)
再考慮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); }