1. 程式人生 > >Gym - 101635K - 凸包+(三分或叉積)

Gym - 101635K - 凸包+(三分或叉積)

題目連結:https://vjudge.net/problem/Gym-101635K

 

解題思路:

尋找最小覆蓋矩形使得能把蛋糕上面所有的點都覆蓋,求出他的寬度,高度不限.

那麼首先求出n個點組成的凸包.列舉凸包上的所有邊,再找凸包上的一個離這條邊最遠的點,經過此點做邊的平行線。

那麼此兩條平行線無限延長肯定能覆蓋所有的點,寬度就是兩平行直線的距離.

取每條邊都這麼做然後最後取答案最小的那一條邊.

那麼怎麼找到邊對應的那個點呢?

1.我們按凸包逆時針點列舉,對應邊的那個最遠點,一定是個"凸值",即逆時針列舉距離值會先增後減,(特殊情況是一開始列舉第一個點就是極值),求凸值就可以用三分來求,因為特殊情況有可能不是凸函式,所以區間至少保留4個以上,3個時退出.

2.叉積法同樣是按逆時針列舉凸包上的點,假設現在考慮邊(i,i+1),做向量(i+1)->i,從(i+1)->(i+2),(i+2)->(i+3)。。。直到兩個叉積>=0

也就是(i+1)->i*j->(j+1)>=0 , 那麼j點就是最遠的,可以用叉積面積方式理解(底是邊(i,i+1),高就是點到直線距離),也可以很容易理解用j這個點做邊的平行線,所有點一定都會被夾在中間.

 

三分法:

#include<math.h>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef long long ll;
const int mx = 2e5 + 10;
int n,top,_x,_y,q[mx*2],m;
double len;
struct node
{
	ll x,y;
}s[mx],tubeg[mx],w;
ll judge(node p1,node p2,node p0)//面積公式判斷正負值 
{
	ll ans = (p1.x-p0.x)*(p2.y-p0.y) - (p2.x-p0.x)*(p1.y-p0.y);
	return ans; 
}
bool cmp(node a,node b)
{
	ll c = judge(w,b,a);//極角排序,同角度按距離從小到大排 
	if(!c) return pow(a.x-w.x,2)+pow(a.y-w.y,2) < pow(b.x-w.x,2)+pow(b.y-w.y,2);
	return c < 0;
}
void Graham()
{
	for(int i=0;i<n;i++)
	{
		while(top>1&&judge(tubeg[top-2],s[i],tubeg[top-1])>=0) top--;//在向量左側的點都去掉 
		tubeg[top++] = s[i];
	}
}
double dist(node a,node b)
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double across(node c,node a,node b)
{
	return fabs((c.y-a.y)*(b.x-a.x) - (c.x-a.x)*(b.y-a.y));
}
double Get(int x,node a,node b)
{
	node c = tubeg[x];
	return across(c,a,b) / len;
}
double sanfen(int l,int r,node a,node b)
{
	while(l<r-2){
		int mid = (l+r)>>1;
		int mmid = mid+1;
		if(Get(q[mid],a,b)>Get(q[mmid],a,b))
		r = mmid;
		else l = mid;
	}
	return max(Get(q[l],a,b),max(Get(q[r],a,b),Get(q[r-1],a,b)));
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		int a,b,p = 0;
		for(int i=0;i<n;i++){
			scanf("%lld%lld",&s[i].x,&s[i].y);
			if(s[i].y<s[p].y) p = i;
			else if(s[i].y==s[p].y&&s[i].x<s[p].x) p = i;
		}
		swap(s[0],s[p]),w.x = s[0].x,w.y = s[0].y;
		sort(s+1,s+n,cmp);//以最下左那個座標為參考座標做極角排序 
		Graham();
		double ans = 1e9;
		int l = 0, r = 0,eng = top-1;
		for(int i=2;i<top;i++) q[r++] = i;
		for(int i=0;i<top;i++){
			len = dist(tubeg[i],tubeg[(i+1)%top]);
			ans = min(ans,sanfen(l,r-1,tubeg[i],tubeg[(i+1)%top]));
			eng = (eng+1)%top;
			l++,q[r++] = eng;
		}
		len =  dist(tubeg[0],tubeg[1]);
		printf("%.10lf\n",ans);
	}
	return 0;
} 

叉積法:

#include<math.h>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef long long ll;
const int mx = 2e5 + 10;
int n,top,_x,_y,m;
double len;
struct node
{
	ll x,y;
}s[mx],tubeg[mx],w;
ll judge(node p1,node p2,node p0)//面積公式判斷正負值 
{
	ll ans = (p1.x-p0.x)*(p2.y-p0.y) - (p2.x-p0.x)*(p1.y-p0.y);
	return ans; 
}
ll judge2(node a,node b)
{
	return a.x*b.y - b.x*a.y;
}
bool cmp(node a,node b)
{
	ll c = judge(w,b,a);//極角排序,同角度按距離從小到大排 
	if(!c) return pow(a.x-w.x,2)+pow(a.y-w.y,2) < pow(b.x-w.x,2)+pow(b.y-w.y,2);
	return c < 0;
}
void Graham()
{
	for(int i=0;i<n;i++)
	{
		while(top>1&&judge(tubeg[top-2],s[i],tubeg[top-1])>=0) top--;//在向量左側的點都去掉 
		tubeg[top++] = s[i];
	}
}
double dist(node a,node b)
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double Get(int x,node a,node b)
{
	node c = tubeg[x];
	return fabs( judge(b,c,a) / len );
}
node cut(node a,node b)
{
	return node{a.x-b.x,a.y-b.y};
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		int a,b,p = 0;
		for(int i=0;i<n;i++){
			scanf("%lld%lld",&s[i].x,&s[i].y);
			if(s[i].y<s[p].y) p = i;
			else if(s[i].y==s[p].y&&s[i].x<s[p].x) p = i;
		}
		swap(s[0],s[p]),w.x = s[0].x,w.y = s[0].y;
		sort(s+1,s+n,cmp);
		Graham();
		double ans = 1e9;
		int head = 1;
		for(int i=0;i<top;i++){
			len = dist(tubeg[i],tubeg[(i+1)%top]);
			node now = cut(tubeg[i],tubeg[(i+1)%top]);
			while(judge2(now,cut(tubeg[(head+1)%top],tubeg[head]))<0)
			head = (head+1)%top;
			ans = min(ans,Get(head,tubeg[i],tubeg[(i+1)%top]));
		}
		printf("%.10lf\n",ans);
	}
	return 0;
}