题目描述:
????????平面上有若干点,任何两个点之间的距离都不会小于0.0001,也不会大于2.0。如果我们用一个半径为1的圆来圈这些点,最多能圈住几个点。
????????注意:如果一个点在圆上,也视为被圈住了。同样,如果点和圆心之间的距离小于1.0001,也视为被圈住了。
输入格式:
????????首先输入一个整数T,表示总共有T组测试数据。 对于每组测试数据,第一行是一个整数N,表示有N个点,1<=N<=300。
输出格式:
????????对于每组测试数据,在一行上输出计算结果。
输入样例:
4 3 6.47634 7.69628 5.16828 4.79915 6.69533 6.20378 6 7.15296 4.08328 6.50827 2.69466 5.91219 3.86661 5.29853 4.16097 6.10838 3.46039 6.34060 2.41599 8 7.90650 4.01746 4.10998 4.18354 4.67289 4.01887 6.33885 4.28388 4.98106 3.82728 5.12379 5.16473 7.84664 4.67693 4.02776 3.87990 20 6.65128 5.47490 6.42743 6.26189 6.35864 4.61611 6.59020 4.54228 4.43967 5.70059 4.38226 5.70536 5.50755 6.18163 7.41971 6.13668 6.71936 3.04496 5.61832 4.23857 5.99424 4.29328 5.60961 4.32998 6.82242 5.79683 5.44693 3.82724 6.70906 3.65736 7.89087 5.68000 6.23300 4.59530 5.92401 4.92329 6.24168 3.81389 6.22671 3.62210
输出样例:
2 5 5 11
解题思路:
????????这道题的意思就是在一个没有界限的范围内给定n个点的坐标,需要你找到一个半径为1的圆来囊括尽量多的点。乍一看上去是不是和贪心算法中经典的雷达问题十分相像,只是把给定的海岸线撤除了而已,但其实不然,看上去只是擦除了一条线,但难度翻了可是不止一倍啊!我们先用这个方法来试一下:
????????雷达问题的解法就是以各个岛屿所在的位置画一个半径等同于雷达搜索半径的圆,然后在海岸线上搜索被最多圆覆盖的区间,来确定合适的位置安装雷达使数量最少。在本题中我们同样可以这样设想,以给定的点为圆心画半径为1的圆,只要我们找到一个被最多圆覆盖的区间,并计算出这个区间被多少个圆覆盖,输出覆盖在这个区间上的圆的个数即可。
????????下面我们来分析一下这个方法的缺点:
- 第一,这个题目没有给定的范围,即题图是一个无界图,这也就给我们程序模拟造成了巨大的困难。
- 第二,如果我们成功的建立出了模拟图,但寻找那个被最多圆覆盖的区间同样是一个问题,程序来寻找这个区间所用的算法是相当麻烦的(我是没能建起来)
- 第三,就算找到这个区间了,再去确定有多少个圆覆盖了这个地方同样是个问题
? ? ? ? 综上,这道题用雷达问题的方法做是非常不理智的行为,希望兄弟们没有和我一样傻傻的在这个坑里打转。
? ? ? ? 正确的(目前我能想到最好的)做法应该是完全使用枚举。枚举所有的点,每两个点可以确定一个圆【因为圆的半径已知】,(两点间距超过圆半径的就确定不了一个圆,跳过这组即可)在这一个圆中保留一个内部点数最多的,加上确定圆的那两个点输出点数即可。若都确定不了一个圆或者测试数据只有一个点,则输出1。
? ? ? ? 当然啦,只要是上过学的人都知道,只有两点间距刚好等于圆的直径才能唯一的确定一个圆,其他情况都会有两个圆,这时候我们选取涵盖最多点的一个就好了。
? ? ? ? 现在我们来看一下确定圆心的方法。由数学可知,已知两点坐标和圆的半径并将其带入方程就可以知道圆心,而编程是不能列方程的,所以我们来推导一下圆心坐标和两点坐标的关系。???????????????????????????????????
? ? ? ? 推导出这样的一个方程我们就可以写代码了。话不多说,上代码:
#include<stdio.h>
#include<math.h>
#define R 1.0
struct Point{
double x, y;
};//结构体用来储存点的坐标
double dist(double x1, double y1, double x2, double y2);//一个用来求两点间距离的函数
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
int i, j, N, ans=1;//ans是保底的数值,最少能覆盖1个点
scanf("%d", &N);
struct Point p[N];
for(i=0;i<N;i++)
scanf("%lf%lf", &p[i].x, &p[i].y);
for(i=0;i<N;i++)
{
for(j=i+1;j<N;j++)
{
if(dist(p[i].x, p[i].y, p[j].x, p[j].y) > 2.0*R)
continue;//间距过大,超过了圆的直径,确定不了圆,跳过本次操作
int k, count=2, num1=0, num2=0;//count初始置2,表示确定圆的两个点
double c1 = 0, c2 = 0, A = 0, B = 0, C = 0, y01 = 0, x01 = 0, x02 = 0, y02 = 0;
c1 = (pow(p[j].x, 2)-pow(p[i].x, 2)+pow(p[j].y, 2)-pow(p[i].y, 2))/2 / (p[j].x-p[i].x);
c2 = (p[j].y-p[i].y) / (p[j].x-p[i].x);
A = 1.0 + pow(c2, 2);
B = 2*(p[i].x-c1)*c2 - 2*p[i].y;
C = pow((p[i].x-c1), 2)+pow(p[i].y, 2)-pow(R, 2);
y01 = (-B + sqrt(B*B - 4 * A * C)) / 2 / A;
x01 = c1 - c2 * y01;//第一个圆的圆心
y02 = (-B - sqrt(B*B - 4 * A * C)) / 2 / A;
x02 = c1 - c2 * y02;//第二个圆的圆心
for(k=0;k<N;k++)//第一个圆心覆盖的点数计算
{
if(k==i || k==j)
continue;
if(dist(p[k].x, p[k].y, x01, y01) <= R)
num1++;
}
for(k=0;k<N;k++)//第二个圆心覆盖的点数计算
{
if(k==i || k==j)
continue;
if(dist(p[k].x, p[k].y, x02, y02) < R)
num2++;
}
count += (num1>=num2) ? num1 : num2;//取能覆盖点数最多的圆保留,并加上在圆上的两个点
ans = (count>=ans) ? count : ans;//最多能覆盖的点数
}
}
printf("%d\n", ans);
}
return 0;
}
double dist(double x1, double y1, double x2, double y2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));//返回两点间的距离
}
? ? ? ? 当然,听有些大佬说用弧度来做就可以不用对确定的两个圆做取舍了。我也想优化一下,但考虑到我那可怜的数学成绩,Emm……一言难尽,想来这个做法也会很难,就没动手。
? ? ? ? 哪位好心的兄台这样做出来的话,欢迎留言来捞我一把,某感激不尽。ノ (○’ω’○)
|