旋转卡壳

csdn没有自动保存吗??我完美的学习计划被躺进医院打破了,等我出来笔记本没电了之前写的啥都没了,好气哦。

目录

  • 旋转卡壳是啥子
    • 对踵点对
  • 怎么旋转怎么卡
  • 转了能够干啥呢
    • 求凸包直径
    • 求凸包的宽
    • 凸多边形间的最大距离
    • 凸多边形间的最小距离
    • 最小面积外接矩形
    • 最小周长外接矩形

旋转卡壳是啥子

2 4 2^4 24种读音的神奇算法,似乎是读作xuán zhuǎn qiǎ ké,顾名思义就是拿两条线旋转着去卡一个凸壳。

对踵点对

与两条平行线相切的两个凸包上的点。

国外大佬证出对踵点对最多 3 ∗ n 2 \frac{3*n}{2} 23n对。
利用旋转卡壳可以在 O ( n ) O(n) O(n)时间内求出我们需要的一些对踵点对。

怎么旋转怎么卡

点点对踵都可以转化成点边对踵,具体我们每个点取它和它下一个点组成的边为它的边,如果两点 i , j i,j i,j对踵, i i i的边不和 j j j对踵,那么 j j j的边一定和 i i i对踵(画图易知)。
我们考虑给每个点的边找一个对踵点,边的对踵点在凸包上显然可以三分,用叉积判面积即可,我们要求n条边的对踵点,且这些点具有单调性,所以并不用三分,双指针扫即可。
注意的是,凸包上有两条边相互平行时,边边对踵会使边的对踵点多达两个,叉积时当面积不变时不再转会得到对角线上较远那对,而继续转会得到相邻较近那对。

转了能够干啥呢

求凸包直径

直径一定出现在对踵点间。若需求平面最远点对,先求凸包即可。
POJ-2187 Beauty Contest
求平面最远点对距离的平方,模板题

//Achen
#include
#include
#include
#include
#include
#include
#include
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=50007;
typedef long long LL;
typedef double db;
using namespace std;
int n,top;
LL ans;

template<typename T>void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct pt{
	LL x,y;
	pt(){}
	pt(LL x,LL y):x(x),y(y){}
	friend bool operator <(const pt&A,const pt&B) {
		return A.x<B.x||(A.x==B.x&&A.y<B.y);
	}
}p[N],ham[N];
pt operator -(const pt&A,const pt&B) { return pt(A.x-B.x,A.y-B.y); }
LL dot(const pt&A,const pt&B) { return A.x*B.x+A.y*B.y; }
LL cross(const pt&A,const pt&B) { return A.x*B.y-A.y*B.x; }
LL lenth(const pt&A) { return dot(A,A); }

bool cmp(const pt&A,const pt&B) {
	return cross(A-p[1],B-p[1])<0||(cross(A-p[1],B-p[1])==0&&lenth(A-p[1])<lenth(B-p[1]));
}

void get_ham(int n) {
	For(i,2,n) if(p[i]<p[1]) swap(p[i],p[1]);
	sort(p+2,p+n+1,cmp); top=0;
	ham[top++]=p[1];
	For(i,2,n) {
		while(top>=2&&cross(p[i]-ham[top-2],ham[top-1]-ham[top-2])<=0) top--;
		ham[top++]=p[i];
	}
}

void RC(int top) {
	ham[top]=ham[0];
	int j=1;
	For(i,0,top-1) {
		while(cross(ham[j]-ham[i],ham[(i+1)%top]-ham[i])<cross(ham[(j+1)%top]-ham[i],ham[(i+1)%top]-ham[i])) j=(j+1)%top;
		ans=max(ans,lenth(ham[i]-ham[j]));	
	}	
}

int main() {
	read(n);
	For(i,1,n) { read(p[i].x); read(p[i].y); }
	get_ham(n);
	RC(top);
	printf("%lld\n",ans);
	Formylove;
}

求凸包的宽

宽定义为平行切线间的最小距离。对每条边的对踵点求点到直线的距离即可。
并没有找到题。

凸多边形间的最大距离

类比凸多边形的直径,在两个多边形上转。没有找到题,可参考下题。

凸多边形间的最小距离

类比凸多边形的宽,在两个多边形上转。
首先最大最小距离仍出现在对踵点上。
过一个多边形做切线,另一个多边形可能会有两条与之平行的切线,给切线定向,只考虑两条反向的切线,最大最小距离一定出现在反向切线上。
找反向切线仍是用叉积找到最大面积,之后双指针找每条边的对踵点,用点到线段的距离更新答案。
上面说过”如果两点 i , j i,j i,j对踵, i i i的边不和 j j j对踵,那么 j j j的边一定和 i i i对踵“,在这里就要在两个凸包上分别转一次。
我的写法是对于第一条边暴力找对踵点,然后边边对踵时,把两个点到线段的距离取min。
POJ-3608 Bridge Across Islands

//Achen
#include
#include
#include
#include
#include
#include
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=10007;
typedef long long LL;
typedef double db;
using namespace std;
int n,m; 
db ans;

template<typename T> void read(T &x) {
	T f=1; x=0; char ch=getchar();
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

#define eps 1e-10
int dcmp(db x) { return fabs(x)<eps?0:(x>0?1:-1); }

struct pt{
	db x,y;
	pt(){}
	pt(db x,db y):x(x),y(y){}
	friend bool operator < (const pt &A,const pt &B) {
		return A.x<B.x||(A.x==B.x&&A.y<B.y);
	}
}pbs,pa[N],pb[N];

pt operator -(const pt &A,const pt &B) { return pt(A.x-B.x,A.y-B.y); }
db cross(const pt &A,const pt &B) { return A.x*B.y-A.y*B.x; }
db dot(const pt &A,const pt &B) { return A.x*B.x+A.y*B.y; }
db lenth(pt A) { return dot(A,A); }

db dis_to_line(pt C,pt A,pt B) {
    db D=sqrt(lenth(A-B));
    if(dcmp(D)==0) return sqrt(lenth(A-C)); 
    else return fabs(cross(C-A,B-A))/D;
}

db dis_to_seg(pt C,pt A,pt B) {
    if(dcmp(cross(C-A,B-A))==0) {
        db lx=min(A.x,B.x),rx=max(A.x,B.x),ly=min(A.y,B.y),ry=max(A.y,B.y);
        if(dcmp(C.x-lx)>=0&&dcmp(C.x-rx)<=0&&dcmp(C.y-ly)>=0&&dcmp(C.y-ry)<=0) return 0;
        else return min(sqrt(lenth(C-A)),sqrt(lenth(C-B)));
    }
    if(dcmp(dot(C-A,B-A))>=0&&dcmp(dot(C-B,A-B))>=0) return dis_to_line(C,A,B);
    else return min(sqrt(lenth(C-A)),sqrt(lenth(C-B)));
}

bool cmp(const pt&A,const pt&B) { 
	return dcmp(cross(A-pbs,B-pbs))<0||(dcmp(cross(A-pbs,B-pbs))==0&&dcmp(lenth(A-pbs)-lenth(B-pbs))<0);
}

void RC(int n,int m,pt a[],pt b[]){
	int j=0;
	a[n]=a[0];
	For(i,1,m-1) if(dcmp(cross(b[j]-a[0],a[1]-a[0])-cross(b[i]-a[0],a[1]-a[0]))<0) j=i;
	For(i,0,n-1) {
		while(i&&(dcmp(cross(b[j%m]-a[i],a[i+1]-a[i])-cross(b[(j+1)%m]-a[i],a[i+1]-a[i]))<0)) j++;
		ans=min(ans,dis_to_seg(b[j%m],a[i],a[i+1]));
        if(dcmp(cross(b[j%m]-a[i],a[i+1]-a[i])-cross(b[(j+1)%m]-a[i],a[i+1]-a[i]))==0)
           ans=min(ans,dis_to_seg(b[(j+1)%m],a[i],a[i+1]));
    }
}

int main() {
	//freopen("std.in","r",stdin);
	//freopen("1.out","w",stdout);
	for(;;) {
		read(n); read(m);
		if(!n&&!m) break;
		For(i,0,n-1) scanf("%lf%lf",&pa[i].x,&pa[i].y);
		For(i,0,m-1) scanf("%lf%lf",&pb[i].x,&pb[i].y);
		For(i,1,n-1) if(pa[i]<pa[0]) swap(pa[i],pa[0]);
		For(i,1,m-1) if(pb[i]<pb[0]) swap(pb[i],pb[0]);
		pbs=pa[0]; sort(pa+1,pa+n,cmp);
		pbs=pb[0]; sort(pb+1,pb+m,cmp);
		ans=1e9;
		RC(n,m,pa,pb);
		RC(m,n,pb,pa);
		printf("%.5lf\n",ans);
	}
	Formylove;
}

最小面积外接矩形

首先有结论,最小面积外接矩形至少有一条边与多边形的边重合。(我不会证,求助QAQ)
那么旋转卡壳枚举所有边点/边边对踵,有了一组平行线可以找到另一组,容易得到一个n^2的做法。
实际上我们需要两组相互垂直的平行线,可以做两次旋转卡壳,且这两组旋转卡壳的单调性是同步的,可以一起转,复杂度依旧为O(n)。
只需要第一组用叉积判断第二组用点积判断。
UVA-10173 Smallest Bounding Rectangle

//Achen
#include
#include
#include
#include
#include
#include
#include
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=50007;
typedef long long LL;
typedef double db;
using namespace std;
int n,top;
db ans;

template<typename T>void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

#define eps 1e-8
int dcmp(db x) { return fabs(x)<eps?0:(x>0?1:-1); }

struct pt{
	db x,y;
	pt(){}
	pt(db x,db y):x(x),y(y){}
	friend bool operator <(const pt&A,const pt&B) {
		return A.x<B.x||(A.x==B.x&&A.y<B.y);
	}
}p[N],ham[N];
pt operator -(const pt&A,const pt&B) { return pt(A.x-B.x,A.y-B.y); }
db dot(const pt&A,const pt&B) { return A.x*B.x+A.y*B.y; }
db cross(const pt&A,const pt&B) { return A.x*B.y-A.y*B.x; }
db lenth(const pt&A) { return sqrt(dot(A,A)); }

db node_to_line(pt C,pt A,pt B) {
	return fabs(cross(C-A,B-A))/lenth(A-B);
}

bool cmp(const pt&A,const pt&B) {
	return dcmp(cross(A-p[1],B-p[1]))<0||(dcmp(cross(A-p[1],B-p[1]))==0&&dcmp(lenth(A-p[1])-lenth(B-p[1]))<0);
}

void get_ham(int n) {
	For(i,2,n) if(p[i]<p[1]) swap(p[i],p[1]);
	sort(p+2,p+n+1,cmp); top=0;
	ham[top++]=p[1];
	For(i,2,n) {
		while(top>=2&&dcmp(cross(p[i]-ham[top-2],ham[top-1]-ham[top-2]))<=0) top--;
		ham[top++]=p[i];
	}
}

void RC(int top) {
	ham[top]=ham[0];
	int j=1,k=1,l=1;
	For(i,0,top-1) {
		while(dcmp(cross(ham[j%top]-ham[i],ham[i+1]-ham[i])-cross(ham[(j+1)%top]-ham[i],ham[i+1]-ham[i]))<0) j++;
		k=max(k,i+1); l=max(l,j);
		while(dcmp(dot(ham[k%top]-ham[i+1],ham[i]-ham[i+1])-dot(ham[(k+1)%top]-ham[i+1],ham[i]-ham[i+1]))>0) k++; 
		while(dcmp(dot(ham[l%top]-ham[i],ham[i+1]-ham[i])-dot(ham[(l+1)%top]-ham[i],ham[i+1]-ham[i]))>0) l++;
		db d=lenth(ham[i+1]-ham[i]);
		db L=fabs(dot(ham[k%top]-ham[i+1],ham[i]-ham[i+1]))/d+fabs(dot(ham[l%top]-ham[i],ham[i+1]-ham[i]))/d+d;
		db D=node_to_line(ham[j%top],ham[i],ham[i+1]);
		ans=min(ans,L*D);
	}
	if(top<3) ans=0;
}

int main() {
    //freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	for(;;) {
		read(n);
		if(!n) break;
		For(i,1,n) scanf("%lf%lf",&p[i].x,&p[i].y);
		get_ham(n);
		ans=1e9;
		RC(top);
		printf("%.4lf\n",ans);
	}
	Formylove;
}

最小周长外接矩形

首先有和最小面积同样的结论(菜如我依然不会证),于是做法同上。
多数情况下这两个矩形是一样的,但不是全部。

你可能感兴趣的:(计算几何)