题解:P3897 [湖南集训] Crazy Rabbit
题意
给定一个半径为
数据范围:
-
1 \le n \le 2000 -
1 \le r, x_i, y_i \le 5000
分析
一开始我并不知道最大团是什么,但是发现可以
这题实际上是一个套着几何外皮的最长上升子序列问题。
考虑除了到圆心的距离,还有哪些刻画线圆位置关系的方式。我们知道从圆外一个点引出圆的双切线,两个切点就能把圆分成两段圆弧,以下称这两段圆弧中任意一段为圆外一点的“对应圆弧”。不难发现以下对应关系:
- 线段所在直线与圆相离
\Leftrightarrow 线段端点的对应圆弧有重合部分,但不存在包含关系。 - 线段所在直线与圆相切
\Leftrightarrow 线段端点的对应圆弧可以接在一起。 - 线段所在直线与圆相交
\Leftrightarrow 线段端点的对应圆弧没有重合部分。
现在我们的问题变成:找到最多的一组点,使它们的对应圆弧两两有重叠但不存在包含关系。把圆剪开拉成一条线段,那么一段圆弧可以看作一个区间。由于两个切点分开的两段圆弧是等价的,所以不用担心有的弧会被剪开,因为只需要取另外与之等价的那段弧就可以了。最后要解决的问题形式化为:
对于区间集合
将所有区间按左端点为第一关键字,右端点为第二关键字排序,枚举每一个区间
最后,我们回过头来看看如何将每个点的对应圆弧映射成区间。考虑以圆心到切点的射线的旋转角(弧度制)作为区间的端点。设
为了能够处理 atan2(y,x)函数。反余弦函数正常使用acos()即可。最后还需要把端点全都调整到
代码
时间复杂度:
#include<bits/stdc++.h>
#define L first
#define R second
using namespace std;
const int N = 2e3 + 10;
const double pi = acos(-1.0);
inline int read(){
int x = 0, neg = 1;
char c = getchar();
while(!isdigit(c)) {if(c == '-') neg = -1; c = getchar();}
while(isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}
return x * neg;
}
int n, r, len, ans;
double mn[N];
vector<double> a;
pair<double, double> q[N];
int main(){
n = read(), r = read();
for(int i = 1; i <= n; i++){
int x = read(), y = read();
double mid = atan2(y, x), dta = acos(r / hypot(x, y));
double L = mid - dta, R = mid + dta;
while(L < -pi) L += 2 * pi;
while(R >= pi) R -= 2 * pi;
if(L > R) swap(L, R);
q[i] = make_pair(L, R);
}
sort(q + 1, q + n + 1);
for(int l = 1; l <= n; l++){
a.clear();
a.push_back(q[l].R);
for(int i = l + 1; i <= n; i++)
if (q[i].L <= q[l].R && q[i].R >= q[l].R) a.push_back(q[i].R);
mn[len = 1] = a[0];
for(int i = 1; i < a.size(); i++){
if(a[i] > mn[len]) mn[++len] = a[i];
else *lower_bound(mn + 1, mn + len + 1, a[i]) = a[i];
}
ans = max(ans, len);
}
printf("%d", ans);
return 0;
}