Boundary
题意:
2D平面上n个点,求这些点落在经过原点(0,0)的圆上的最大数量
思路:
有很多解法。
(1)
比赛时根据圆的标准式
(
x
−
a
)
2
+
(
y
−
b
)
2
=
r
2
(x-a)^{2}+(y-b)^{2}=r^2
(x−a)2+(y−b)2=r2
又因为圆过原点
(
0
,
0
)
(0,0)
(0,0) ,所以
r
2
=
a
2
+
b
2
r^2=a^2+b^2
r2=a2+b2
结合两个式子进行化简可得:
x
2
+
y
2
=
2
a
x
+
2
b
y
x^2+y^2=2ax+2by
x2+y2=2ax+2by
选取两个点
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
(x_1,y_1),(x_2,y_2)
(x1,y1),(x2,y2) 可以解方程
{
x
1
2
+
y
1
2
=
2
a
x
1
+
2
b
y
1
x
2
2
+
y
2
2
=
2
a
x
2
+
2
b
y
2
\begin{cases} x_1^2+y_1^2=2ax_1+2by_1\\ x_2^2+y_2^2=2ax_2+2by_2 \end{cases}
{x12+y12=2ax1+2by1x22+y22=2ax2+2by2
得到
a
,
b
a,b
a,b 的值,统计相同a,b的次数,得到最大次数,但是这并不是答案,因为同一个点会被统计多次。例如点1,2,3,4在同一个圆上,但是(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)都会被统计到,一共6次。
可以发现这是边数和点数之间的关系,边数为统计到的相同的最大次数,而点数即为答案。假设点数为n,边数为
r
e
s
=
C
n
2
=
n
(
n
−
1
)
2
res = C^{2}_{n}=\cfrac{n(n-1)}{2}
res=Cn2=2n(n−1)
所以,答案
n
=
⌊
r
e
s
×
2
⌋
+
1
n=⌊\sqrt{res\times2}⌋+1
n=⌊res×2⌋+1
注意:
1、统计最大次数不可以用map,map会边插入边排序,原来
O
(
n
2
)
O(n^2)
O(n2) 的复杂度就变成了
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn) 只能过70%的数据无论是方法一还是方法二,但是官方题解复杂度也是
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn)却能AC就很玄学。
所以用vector存圆心,所有点枚举完后排序,使相邻的点排在一起,统计相同点的最大次数。
2、解方程涉及除法,特判除数为0的情况,此时有两种情况:一是
x
1
,
y
2
x1,y2
x1,y2 或
x
2
,
y
1
x2,y1
x2,y1 不为零,此时两点一个在x轴一个在y轴上,其他情况下说明三点共线,不存在圆,方程无解。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2e3+5;
struct point
{
double x, y;
};
double dis2(point a, point b)
{
return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
}
point a[N];
int main()
{
vector<pair<double,double>> ans;
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++ i)
scanf("%lf%lf", &a[i].x, &a[i].y);
for(int i = 1; i <= n; ++ i)
{
for(int j = i+1;j <= n; ++ j)
{
double x1 = a[i].x, y1 = a[i].y;
double x2 = a[j].x, y2 = a[j].y;
double r1 = x1 * x1 + y1 * y1, r2 =x2 * x2 + y2 * y2;
r1/=2.0,r2/=2.0;
double b = r2*x1 - r1*x2;
double c = y2*x1 - y1*x2;
double a = r1*y2 - r2*y1;
if(c == 0.0)
{
if((x1 == 0.0 && y2 == 0.0)||(y1 == 0.0 && x2 == 0.0))
{
a = (x1 + x2) / 2.0;
b = (y1 + y2) / 2.0;
pair<double, double> tmp;
tmp.first = a, tmp.second = b;
ans.push_back(tmp);
}
else continue;
}
else
{
a /= c, b/= c;
pair<double, double> tmp;
tmp.first = a, tmp.second = b;
ans.push_back(tmp);
}
}
}
sort(ans.begin(),ans.end());
int res = 0, cnt = 0;
pair<double, double> tmp = ans[0];
for(auto i: ans)
{
if(i == tmp)
{
cnt++;
}
else{
res = max(res, cnt);
cnt = 1;
tmp = i;
}
}
res = max(cnt, res);
printf("%d", (int)sqrt(res*2) + 1);
return 0;
}
(2)
更为简单的写法,其实差不多,不过这里可以用求三角形外心的模板,少了解方程里的推公式步骤。
遍历选取两个点和原点组成三角形,求三角形的外心并将点存起来,找出被覆盖最多次数的点即为圆心,次数的意义和方法(1)一样,是在圆上的点组成的多边形的边数,得到边数后求(int)sqrt(ans*2) +1就是答案。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <vector>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<double, double> p;
const int N = 2005;
struct point
{
ll x, y;
};
point a[N];
vector<p> cc;
inline void getc(const point& p1, const point& p2, const point& p3, p& center) //求三角形外心
{
ll x1 = p1.x;
ll x2 = p2.x;
ll x3 = p3.x;
ll y1 = p1.y;
ll y2 = p2.y;
ll y3 = p3.y;
ll t1=x1*x1+y1*y1;
ll t2=x2*x2+y2*y2;
ll t3=x3*x3+y3*y3;
double temp=x1*y2+x2*y3+x3*y1-x1*y3-x2*y1-x3*y2;
center.first = double(t2*y3+t1*y2+t3*y1-t2*y1-t3*y2-t1*y3)/temp;
center.second = double(t3*x2+t2*x1+t1*x3-t1*x2-t2*x3-t3*x1)/temp;
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++ i)
scanf("%lld%lld", &a[i].x, &a[i].y);
point ori;
ori.x = 0, ori.y = 0;
for(int i = 1; i <= n; ++ i)
{
for(int j = i + 1; j <= n; ++ j)
{
p c;
if(a[i].x * a[j].y != a[i].y * a[j].x)
{
getc(a[i], a[j], ori, c);
cc.push_back(c);
}
}
}
sort(cc.begin(),cc.end());
int ans = 0, cnt = 0;
p tmp = cc[0];
for(auto i: cc)
{
if(i == tmp)
{
cnt++;
}
else{
ans = max(ans, cnt);
cnt = 1;
tmp = i;
}
}
ans = max(cnt, ans);
printf("%d", int(sqrt(ans*2))+1);
return 0;
}
(3)标程做法,较为复杂