题意:给定二维平面上不超过50000个点,求最远点对距离的平方。
思路:由数据量来判断直接枚举会超时。注意到最远距离点对必出现在这些点的凸包上,所以可以先求出凸包,然后在凸包上枚举。此法的最坏情况复杂度仍然是n^2的,但是可以AC这道题了。在复杂度意义下的优化是旋转卡壳(参考http://www.cppblog.com/staryjy/archive/2009/11/19/101412.html):
如果qa,qb是凸包上最远两点,必然可以分别过qa,qb画出一对平行线。通过旋转这对平行线,我们可以让它和凸包上的一条边重合,如图中蓝色直线,可以注意到,qa是凸包上离p和qb所在直线最远的点。于是我们的思路就是枚举凸包上的所有边,对每一条边找出凸包上离该边最远的顶点,计算这个顶点到该边两个端点的距离,并记录最大的值。直观上这是一个O(n2)的算法,和直接枚举任意两个顶点一样了。但是注意到当我们逆时针枚举边的时候,最远点的变化也是逆时针的,这样就可以不用从头计算最远点,而可以紧接着上一次的最远点继续计算(详细的证明可以参见上面链接中的论文)。于是我们得到了O(n)的算法。
凸包上枚举:
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <cstdlib> using namespace std; #define clc(s,t) memset(s,t,sizeof(s)) #define INF 0x3fffffff #define N 50005 struct point{ int x,y; }p[N]; int n; int stack[N],top = -1; int multi(struct point a,struct point b,struct point c){ return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); } int dis(struct point a,struct point b){ return (b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y); } int cmp(struct point a,struct point b){ int tmp = multi(p[1],a,b); if(tmp == 0) return dis(p[1],a) < dis(p[1],b); return tmp>0; } int main(){ int i,j,res=0; struct point begin; scanf("%d",&n); begin.x = begin.y = 10005; for(i = 1;i<=n;i++){ scanf("%d %d",&p[i].x,&p[i].y); if(p[i].y < begin.y){ begin = p[i]; j = i; }else if(p[i].y==begin.y && p[i].x<begin.x){ begin = p[i]; j = i; } } p[j] = p[1]; p[1] = begin; sort(p+2,p+n+1,cmp); stack[++top] = 1; stack[++top] = 2; for(i = 3;i<=n;i++){ while(top>0 && multi(p[stack[top-1]], p[stack[top]], p[i])<=0) top--; stack[++top] = i; } for(i = 0;i<top;i++) for(j = i+1;j<=top;j++) res = max(res,dis(p[stack[i]],p[stack[j]])); printf("%d\n",res); }
旋转卡壳:
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <cstdlib> using namespace std; #define clc(s,t) memset(s,t,sizeof(s)) #define INF 0x3fffffff #define N 50005 struct point{ int x,y; }p[N]; int n; int stack[N],top = -1; int multi(struct point a,struct point b,struct point c){ return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); } int dis(struct point a,struct point b){ return (b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y); } int cmp(struct point a,struct point b){ int tmp = multi(p[1],a,b); if(tmp == 0) return dis(p[1],a) < dis(p[1],b); return tmp>0; } int main(){ int i,j,res=0; struct point begin; scanf("%d",&n); begin.x = begin.y = 10005; for(i = 1;i<=n;i++){ scanf("%d %d",&p[i].x,&p[i].y); if(p[i].y < begin.y){ begin = p[i]; j = i; }else if(p[i].y==begin.y && p[i].x<begin.x){ begin = p[i]; j = i; } } if(n==2){ printf("%d\n",dis(p[1],p[2])); return 0; } p[j] = p[1]; p[1] = begin; sort(p+2,p+n+1,cmp); stack[++top] = 1; stack[++top] = 2; for(i = 3;i<=n;i++){ while(top>0 && multi(p[stack[top-1]], p[stack[top]], p[i])<=0) top--; stack[++top] = i; }//至此用graham法求得凸包,下面是旋转卡壳法 j = 1; stack[++top] = 1; for(i = 0;i<top;i++){ while(multi(p[stack[i]],p[stack[i+1]],p[stack[j+1]]) > multi(p[stack[i]], p[stack[i+1]], p[stack[j]])) j = (j+1)%top;//逆时针枚举距离线段(p[stack[i]],p[stack[i+1]])最远的点j res = max(res,dis(p[stack[i]],p[stack[j]])); } printf("%d\n",res); }