旋转卡壳证明,代码

问题:求散点图中最远两点距离

算法:
      首先有几个定义:

        支撑线:使得所有点都在线上或一侧的一种线

        对踵点:过两点的所有平行线对中存在一对支撑线的点

        凸多边形直径:凸多边形所有端点中距离最远的一对点之间的距离(和图的直径很像,但图的直径是simple path,更加抽象没有几何性质,相当于比凸多边形直径要弱)

     然后是算法:

        引理 1: 距离最远的点对一定是散点图的凸包上的端点

        证明:如果存在点对(A,B),其中B点不是凸包上的端点,那么向着这个点的方向作射线和凸包的l边相交于点C。射线和l交出的在凸包内部的两个角中一定有一个大于等于90度。把这个角一侧凸包上最靠近点C的端点和A点连接,形成一个直角三角形或者钝角三角形。AC为钝角/直角的对边,显然比AB更长,与前提条件矛盾。

        引理 2:距离最远的点对一定是对踵点

        证明:连接距离最远的点对(A,B),在A,B处分别作AB垂线l1,l2。对于l1,l2,称AB一侧为它们的“内侧”,另一侧为“外侧”。对于l1,如果有散点C在“外侧”或l1上,那么三角形ABC为钝角或直角三角形,钝角/直角对边为AC,则AC>AB,与前提条件矛盾。l2同理。那么l1,l2为AB的一对平行的支撑线,故A,B为一对对踵点。

        

        引理 3:对踵点中一定有一个点是所有端点中距离另一个点一条凸包上临边最远的点

        证明:朝一个方向同时旋转引理2中得出的一堆支撑线,其中一条支撑线会先和凸包上的一条边重合(图中l2)。此时,易证其支撑线没有和凸包边重合的点对中的点(图中A点)是距离这条支撑线(图中l2),也就是B的一条凸包上临边最远的点。

        

算法流程:
        1. 求凸包

        2. 下凸壳上凸壳双指针枚举所有图中红三角形ABC这样的三角形,根据三个引理,最长距离点对一定在所有枚举到的三角形的非凸壳边中(就是所有的AB,AC)。

代码:

洛谷P1452板子

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <utility>
#include <vector>
using namespace std;
const int N=5e4+5;
const double eps=1e-9;
pair<double,double> a[N];
int n;
int mmax;
inline char gc()
{
	static char BB[100000],*S=BB,*T=BB;
	return S==T&&(T=(S=BB)+fread(BB,1,100000,stdin),S==T)?EOF:*S++;
}
inline int read()
{
	int ret=0,f=1;
	char ch=gc();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=gc();
	}
	while(ch>='0'&&ch<='9')
	{
		ret=ret*10+ch-'0';
		ch=gc();
	}
	return ret*f;
}
bool Cmp(pair<int,int> x,pair<int,int> y) { return x.first==y.first?x.second<y.second:x.first<y.first;}
struct edge
{
	double lean;
	pair<int,int> a,b;
};
vector<edge> w,u;
pair<int,int> stack[N];
int top;
void cal(vector<edge> &w,bool (*check)(pair<int,int> x,pair<int,int> y,pair<int,int> z))
{
	top=0;
	for(int i=1;i<=n;++i)
	{
		//if(top<2||check(stack[top-1],stack[top],a[i])) stack[++top]=a[i];
		while(top>1&&!check(stack[top-1],stack[top],a[i]))top--;
		stack[++top]=a[i];
	}
//	cout<<"cal"<<endl;
//	cout<<"top"<<top<<endl;
//	for(int i=1;i<=top;++i) cout<<stack[i].first<<" "<<stack[i].second<<endl;
	for(int i=1;i<top;++i)
		w.push_back(edge{((stack[i+1].second-stack[i].second)*1.0)/(stack[i+1].first-stack[i].first+eps),stack[i],stack[i+1]});//注意这里考虑最左边/右边是竖着的线段的情况,把竖着的线段处理成很大的斜率
}
bool cmp1(pair<int,int> a,pair<int,int> b,pair<int,int> c)//anticlockwise
{
	double dx1=b.first-a.first,dx2=c.first-b.first,dy1=b.second-a.second,dy2=c.second-b.second;
	return dx1*dy2-dx2*dy1>=(-1)*eps;
}
bool cmp2(pair<int,int> a,pair<int,int> b,pair<int,int> c)//clockwise
{
	int dx1=b.first-a.first,dx2=c.first-b.first,dy1=b.second-a.second,dy2=c.second-b.second;
	return dx1*dy2-dx2*dy1<=eps;
}
int dis(pair<int,int> x,pair<int,int> y){return (x.first-y.first)*(x.first-y.first)+(x.second-y.second)*(x.second-y.second);}
void sol()
{
	vector<edge>::iterator x=w.begin(),y=u.end();
	y--;
	for(;x!=w.end();x++)//单独处理卡壳上最左和最右边的节点,这里遍历不到
	{
		while(y>u.begin()&&(y-1)->lean<x->lean) y--;
		if(y>u.begin())
		       if(y->lean<=x->lean&&(y-1)->lean>=x->lean)
		       		mmax=max(mmax,max(dis(x->a,y->a),dis(x->b,y->a)));
	}
	x=w.begin();
	y=u.end();y--;
	for(;y>=u.begin();y--)
	{
		while(x!=w.end()&&(x+1)->lean<y->lean) x++;
		if(x<w.end()-1)
			if((x+1)->lean>=y->lean&&x->lean<=y->lean)
				mmax=max(mmax,max(dis(y->a,x->b),dis(y->b,x->a)));
	}
//	cout<<"mmax"<<mmax<<endl;
	//单独处理
	if(!u.empty()&&!w.empty())
	{
		pair<int,int> d[4]={u.begin()->a,(u.end()-1)->b,w.begin()->a,(w.end()-1)->b};
		for(int i=1;i<=n;++i)
		for(int j=0;j<4;++j) mmax=max(mmax,dis(d[j],a[i]));
	}
}
int main()
{
//	freopen("in.in","r",stdin);
	n=read();
	for(int i=1;i<=n;++i) a[i].first=1.0*read(),a[i].second=1.0*read();
	sort(a+1,a+1+n,Cmp);
//	for(int i=1;i<=n;++i) cout<<a[i].first<<" "<<a[i].second<<endl;
	cal(w,cmp1);cal(u,cmp2);
//	vector<edge>::iterator it=w.begin();
//	while(it!=w.end()){ cout<<it->lean<<endl;it++;}
//	cout<<"**"<<endl;
//	it=u.begin();
//	while(it!=u.end()){ cout<<it->lean<<endl;it++;}
	sol();
	cout<<mmax<<endl;
//	fclose(stdin);
//	cout<<"try"<<endl;
//	pair<int,int> t=make_pair(0,1);
//	cout<<dis(make_pair(0,0),make_pair(1,1))<<endl;
	return 0;
}

以下是旋转卡壳求最小外接矩形的C++代码: ```c++ #include <iostream> #include <algorithm> #include <cmath> using namespace std; const int MAXN = 100005; const double eps = 1e-8; const double pi = acos(-1.0); struct Point { double x, y; Point() {} Point(double x, double y): x(x), y(y) {} }; Point operator + (Point a, Point b) { return Point(a.x + b.x, a.y + b.y); } Point operator - (Point a, Point b) { return Point(a.x - b.x, a.y - b.y); } Point operator * (Point a, double p) { return Point(a.x * p, a.y * p); } Point operator / (Point a, double p) { return Point(a.x / p, a.y / p); } double Dot(Point a, Point b) { return a.x * b.x + a.y * b.y; } double Cross(Point a, Point b) { return a.x * b.y - a.y * b.x; } double Length(Point a) { return sqrt(Dot(a, a)); } double Angle(Point a, Point b) { return acos(Dot(a, b) / Length(a) / Length(b)); } bool cmp(Point a, Point b) { if (a.x != b.x) return a.x < b.x; return a.y < b.y; } int n; Point p[MAXN], ch[MAXN], u[MAXN], v[MAXN]; void Andrew() { sort(p, p + n, cmp); int m = 0; for (int i = 0; i < n; i++) { while (m > 1 && Cross(ch[m - 1] - ch[m - 2], p[i] - ch[m - 2]) < 0) m--; ch[m++] = p[i]; } int k = m; for (int i = n - 2; i >= 0; i--) { while (m > k && Cross(ch[m - 1] - ch[m - 2], p[i] - ch[m - 2]) < 0) m--; ch[m++] = p[i]; } if (n > 1) m--; ch[m] = ch[0]; } double RotatingCalipers() { double ans = 1e18; int j = 1, k = 1, l = 1; for (int i = 0; i < n; i++) { while (fabs(Cross(ch[i + 1] - ch[i], ch[j + 1] - ch[i])) > fabs(Cross(ch[i + 1] - ch[i], ch[j] - ch[i]))) j = (j + 1) % n; while (fabs(Dot(ch[i + 1] - ch[i], ch[k + 1] - ch[i])) > fabs(Dot(ch[i + 1] - ch[i], ch[k] - ch[i]))) k = (k + 1) % n; if (i == 0) l = k; while (fabs(Dot(ch[i + 1] - ch[i], ch[l + 1] - ch[i])) > fabs(Dot(ch[i + 1] - ch[i], ch[l] - ch[i]))) l = (l + 1) % n; Point u = ch[j] - ch[i], v = ch[k] - ch[i], w = ch[l] - ch[i]; double angle = Angle(u, v); double len = Length(u); double h = fabs(Cross(u, w)) / len; double w1 = len * sin(angle), w2 = sqrt(Length(v) * Length(v) - h * h); ans = min(ans, w1 * w2); } return ans; } int main() { cin >> n; for (int i = 0; i < n; i++) { cin >> p[i].x >> p[i].y; } Andrew(); double ans = RotatingCalipers(); printf("%.2lf\n", ans); return 0; } ``` 其中 `Andrew()` 函数是求凸包的函数,`RotatingCalipers()` 函数是旋转卡壳求最小外接矩形的函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值