1. 程式人生 > >BZOJ 1027 JSOI2007 合金 計算幾何+Floyd

BZOJ 1027 JSOI2007 合金 計算幾何+Floyd

_id sni memset ems jsb b2c mat eas fab

題目大意:給定一些合金,選擇最少的合金,使這些合金能夠按比例合成要求的合金

首先這題的想法特別奇異 看這題幹怎麽會想到計算幾何 並且計算幾何又怎麽會跟Floyd掛邊 好強大

首先因為a+b+c=1 所以我們僅僅要得到a和b就可以 c=1-a-b 所以c能夠不讀入了

然後我們把每種原料抽象成一個點 可知兩個點能合成的合金一定在兩點連線的線段上

證明:設兩個點為(x1,y1)和(x2,y2),新合成的合金為(ax1+bx2,ay2+by2) (a+b=1,a,b>0) 兩點連線為(y-y1)/(x-x1)=(y-y2)/(x-x2),代入就可以得證

那麽我們選定一些原料。這些原料能合成的合金一定在這些點所在的凸包上 證明略

於是我們就把問題轉化成了這樣:給定兩個點集A和B,求A中最小的一個子集S。使B中全部的點在S的凸包內部

這個問題怎麽處理呢?這裏用到一個十分巧妙的方法

技術分享

如圖,枚舉A點集兩點i,j(i能夠等於j)若B點集中的全部點都在向量i->j的左側或線段ij上(圖中紅色的點)而沒有點在圖中綠色的點所在位置。就連接一條i->j的單向邊

即 若隨意B點集中的點k滿足(k->i)×(k->j)<0||(k->i)×(k->j)==0&&(k->i)·(k->j)<=0 則連接一條i->j的單向邊

然後Floyd求最小環就可以

正確性自己YY吧 這樣寫應該是能夠降低非常多討論而且不會被卡掉的 順便吐槽一句數據實在太弱……

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 510
#define INF 0x3f3f3f3f
#define EPS 1e-7
using namespace std;
struct point{
	double x,y;
	point operator - (const point z)
	{
		point re;
		re.x=x-z.x;
		re.y=y-z.y;
		return re;
	}
	double operator * (const point z)
	{
		return x*z.y-y*z.x;
	}
	double operator ^ (const point z)//大家好我是萌萌噠的點積 乘號被叉積搶了主人僅僅能給我這個了~ 
	{
		return x*z.x+y*z.y;
	}
}a[M],b[M];
int m,n;
int map[M][M],f[M][M];
int ans=INF;
void Floyd()
{
	int i,j,k;
	memcpy(f,map,sizeof f);
	for(k=1;k<=m;k++)
		for(i=1;i<=m;i++)
			if(f[i][k]<INF)
				for(j=1;j<=m;j++)
					f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
	for(i=1;i<=m;i++)
		ans=min(ans,f[i][i]);
}
int main()
{
	int i,j,k;
	memset(map,0x3f,sizeof map);
	cin>>m>>n;
	for(i=1;i<=m;i++)
		scanf("%lf%lf%*lf",&a[i].x,&a[i].y);
	for(i=1;i<=n;i++)
		scanf("%lf%lf%*lf",&b[i].x,&b[i].y);
	for(i=1;i<=m;i++)
		for(j=1;j<=m;j++)
		{
			for(k=1;k<=n;k++)
			{
				double cross=(a[i]-b[k])*(a[j]-b[k]);
				if( cross>EPS )
					break;
				if( fabs(cross)<EPS && (a[i]-b[k]^a[j]-b[k])>EPS )
					break;
			}
			if(k==n+1)
				map[i][j]=1;
		}
	Floyd();
	if(ans==INF)
		ans=-1;
	cout<<ans<<endl;
}


BZOJ 1027 JSOI2007 合金 計算幾何+Floyd