-
题目
-
题意
开始复习自己之前的代码。竟然没有想到,我竟然学过平面最近点对。
题目的背景是套圈游戏。作为一名商人,为了最大化自己的利益,这个圈只能套住一个玩具。但是又要吸引更多的顾客,这个圈就得尽可能的大。
求这个圈的半径。
最近平面点对的裸题了。
-
平面最近点对 (分治法)
首先,将所有的点按照x坐标排序,将坐标尽量分成相等数量的两部分。
然后,通过递归求出这两部分的最近点对的距离d1,d2(两个,三个的时候可以直接求得)。
但是,所有点的最近点对并不一定等于 min(d1,d2)。需要进行分治后的合并操作,如下(没有用到7,鸽巢的优化):
第一,把所有的点中,可能形成的最小值点放入一个集合possible中。这个可能形成的点的横坐标范围在
[ 中间点坐标 - min(d1,d2) , 中间点坐标 + min(d1,d2)]。
第二, 直接对possible集合中的点,一对一对进行遍历,求出最小值。最糟糕的情况下,复杂度堪比直接暴力搜索。所以,进 行优化。
第三,优化就是,将possible集合中的点,按照y坐标进行排序。在一对一对顺序地进行遍历的过程中,如果两点的纵坐标大于了min(d1,d2),break后进行下一次循环。
-
代码
//1007 套圈,给出N个物体的坐标,让圈一次只能套到一个物体,求圈长度的最大值
//最近点对问题---分治法
//暴力循环--tle
//AC 如果参数总是0,注意有没有传进去参数,是不是重复定义了!
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100005
#define INf 0x3f3f3f
using namespace std;
struct point
{
double x;
double y;
};
point p[N];
int possible[N];
double dis(point a,point b)
{
double xx=a.x-b.x;
double yy=a.y-b.y;
return sqrt(xx*xx+yy*yy);
}
bool cmpx(point a,point b)
{
return a.x<b.x;
}
bool cmpy(int a,int b)
{
return p[a].y<p[b].y;
}
double divide(int l,int r)
{
if(r==l+1)
return dis(p[l],p[r]);
if(r==l+2)
return min(min(dis(p[l],p[l+1]),dis(p[l],p[r])),dis(p[l+1],p[r]));
int mid=(l+r)/2;
double mindis=min(divide(l,mid),divide(mid+1,r));
int pos_num=0;
for(int i=l;i<=r;i++)
{
if(p[i].x>=p[mid].x-mindis && p[i].x<=p[mid].x+mindis)
{
possible[pos_num++]=i;
}
}
sort(possible,possible+pos_num,cmpy);
for(int i=0;i<pos_num;i++)
{
for(int j=i+1;j<pos_num;j++)
{
if(p[possible[j]].y-p[possible[i]].y>=mindis)
break;
mindis=min(mindis,dis(p[possible[i]],p[possible[j]]));
}
}
return mindis;
}
int main()
{
int n;
// freopen("a.txt","r",stdin);
while(scanf("%d",&n)!=EOF&&n)
{
// point p[N];
for(int i=0;i<n;i++)
{
scanf("%lf%lf",&p[i].x,&p[i].y);
// cout<<"dis"<<dis(p[0],p[i])<<" ";
}
sort(p,p+n,cmpx);
// cout<<endl;
// for(int i=0;i<n;i++)
// cout<<p[i].x<<" "<<p[i].y<<endl;
double ans=divide(0,n-1);
printf("%.2f\n",ans/2);
}
}
#include <ctime>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
// 分治法求解最近点对问题
// @`13
// 2017年4月21日
// 参考 : http://blog.csdn.net/to_baidu/article/details/50315607
// 参考 : http://www.cnblogs.com/king1302217/archive/2010/07/08/1773413.html
#define INFINITE_DISTANCE 65535 // 无限大距离
#define COORDINATE_RANGE 100.0 // 横纵坐标范围为[-100,100]
#ifndef Closest_pair
typedef struct Point
{// 二维坐标上的点Point
double x;
double y;
}Point;
double Distance(Point a, Point b)
{//平面上任意两点对之间的距离公式计算
return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
bool compareX(Point a, Point b)
{//自定义排序规则:依照结构体中的x成员变量升序排序
return a.x < b.x;
}
bool compareY(Point a, Point b)
{//自定义排序规则:依照结构体中的x成员变量升序排序
return a.y < b.y;
}
float ClosestPair(Point points[], int length, Point &a, Point &b)
{// 求出最近点对记录,并将两点记录再a、b中
double distance; //记录集合points中最近两点距离
double d1, d2; //记录分割后两个子集中各自最小点对距离
int i = 0, j = 0, k = 0, x = 0; //用于控制for循环的循环变量
Point a1, b1, a2, b2; //保存分割后两个子集中最小点对
if (length < 2)
return INFINITE_DISTANCE; //若子集长度小于2,定义为最大距离,表示不可达
else if (length == 2)
{//若子集长度等于2,直接返回该两点的距离
a = points[0];
b = points[1];
distance = Distance(points[0], points[1]);
}
else
{//子集长度大于3,进行分治求解
Point *pts1 = new Point[length]; //开辟两个子集
Point *pts2 = new Point[length];
sort(points, points + length, compareX); //调用algorithm库中的sort函数对points进行排序,compareX为自定义的排序规则
double mid = points[(length - 1) / 2].x; //排完序后的中间下标值,即中位数
for (i = 0; i < length / 2; i++)
pts1[i] = points[i];
for (int j = 0, i = length / 2; i < length; i++)
pts2[j++] = points[i];
d1 = ClosestPair(pts1, length / 2, a1, b1); //分治求解左半部分子集的最近点
d2 = ClosestPair(pts2, length - length / 2, a2, b2); //分治求解右半部分子集的最近点
if (d1 < d2) { distance = d1; a = a1; b = b1; } //记录最近点,最近距离
else { distance = d2; a = a2; b = b2; }
//merge - 进行子集合解合并
//求解跨分割线并在δ×2δ区间内的最近点对
Point *pts3 = new Point[length];
for (i = 0, k = 0; i < length; i++) //取得中线2δ宽度的所有点对共k个
if (abs(points[i].x - mid) <= distance)
pts3[k++] = points[i];
sort(pts3, pts3 + k, compareY); // 以y排序矩形阵内的点集合
for (i = 0; i < k; i++)
{
if (pts3[j].x - mid >= 0) // 只判断左侧部分的点
continue;
x = 0;
for (j = i + 1; j <= i + 6 + x && j < k; j++) //只需与有序的领接的的6个点进行比较
{
if (pts3[j].x - mid < 0)
{// 假如i点是位于mid左边则只需判断在mid右边的j点即可
x++;
continue;
}
if (Distance(pts3[i], pts3[j]) < distance)
{//如果跨分割线的两点距离小于已知最小距离,则记录该距离和两点
distance = Distance(pts3[i], pts3[j]);
a = pts3[i];
b = pts3[j];
}
}
}
}
return distance;
}
void SetPoints(Point *points, int length)
{//随机函数对点数组points中的二维点进行初始化
srand(unsigned(time(NULL)));
for (int i = 0; i < length; i++)
{
points[i].x = (rand() % int(COORDINATE_RANGE * 200)) / COORDINATE_RANGE - COORDINATE_RANGE;
points[i].y = (rand() % int(COORDINATE_RANGE * 200)) / COORDINATE_RANGE - COORDINATE_RANGE;
}
}
int main()
{
int num; //随机生成的点对个数
Point a, b; //最近点对
double diatance; //点对距离
cout << "请输入二维点对个数:";
cin >> num;
if (num < 2)
cout << "请输入大于等于2的点个数!!" << endl;
else
{
cout << endl << "随机生成的" << num << "个二维点对如下:" << endl;
Point *points = new Point[num];
SetPoints(points, num);
for (int i = 0; i < num; i++)
cout << "(" << points[i].x << "," << points[i].y << ")" << endl;
diatance = ClosestPair(points, num, a, b);
cout << endl << endl << "按横坐标排序后的点对:" << endl;
for (int i = 0; i < num; i++)
cout << "(" << points[i].x << "," << points[i].y << ")" << endl;
cout << endl << "最近点对为:" << "(" << a.x << "," << a.y << ")和" << "(" << b.x << "," << b.y << ")" << endl << "最近点对距离为:" << diatance << endl;
}
system("pause");
}
#endif // !Closest_pair