【洛谷3897】[湖南集训] Crazy Rabbit(趣题)

点此看题面

  • 给定一个圆和圆外(n)个点。
  • 问最多能找出多少个点,使得它们之间两两的连线都不与圆相交。
  • (nle2 imes10^3)

圆的巧妙转化

容易想到去求出每个点与圆的两条切线,那么在这两条切线之间的其他点与该点的连线都会与圆相交,而切线外的点则不会。

实际上,我们把每个点与圆的两个切点看成一个区间(具体实现中,方便起见我们我们用角度来表示这个区间),那么点(A)与其两条切线间的点(B),对应的区间必然包含(两点在圆的同侧)或是相离(两点在圆的异侧),也就是说两点连线无交当且仅当它们的区间是相交的。

这个结论自己画图理解一下吧,感觉很难理性地证明。

要求角度很简单,首先我们求出当前点的角度(g),然后求出当前点与圆心的连线和圆心向切线的垂线的夹角(d)(cos d=frac{sqrt{x^2+y^2}}{R})),那么(g±d)就是对应的区间。(具体实现中最好把左右端点表示到([-pi,pi])中,注意这里的区间包含或是相离是一样的,因此即使交换左右端点也没有关系。)

然后题意就被转化为在一个序列上选出若干个区间满足两两相交。

我们先将所有区间按左端点排序,不妨枚举最左边的区间,然后对之后所有满足与该区间相交的区间,按照右端点求一遍最长上升子序列,即可求出答案。

代码:(O(n^2logn))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define DB double
using namespace std;
int n,R;DB f[N+5];const DB Pi=acos(-1);
struct Il
{
	DB l,r;I Il(Con DB& a=0,Con DB& b=0):l(min(a,b)),r(max(a,b)){}
	I bool operator < (Con Il& o) Con {return l<o.l;}//按左端点排序
}s[N+5];
int main()
{
	RI i,j,x,y;DB l,r,g,d;for(scanf("%d%d",&n,&R),i=1;i<=n;++i) scanf("%d%d",&x,&y),
		g=atan2(y,x),d=acos(R/sqrt(x*x+y*y)),(l=g-d)<-Pi&&(l+=2*Pi),(r=g+d)>Pi&&(r-=2*Pi),s[i]=Il(l,r);//求出角度对应区间
	RI t,ans=0;for(sort(s+1,s+n+1),i=1;i<=n;ans=max(ans,t+1),++i) for(f[t=0]=s[i].r,j=i+1;
		j<=n&&s[j].l<=s[i].r;++j) s[j].r>s[i].r&&(f[f[t]<=s[j].r?++t:lower_bound(f+1,f+t+1,s[j].r)-f]=s[j].r);//最长上升子序列
	return printf("%d
",ans),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu3897.html