题意非常清真,给你一堆点和一个圆半径,选一个圆心让覆盖的点的点权和最大
首先,傻傻的做法,暴力微分枚举点,暴力统计答案.玄学复杂度
这个傻傻的代码虽然不是重点,还是贴一张图片吧
然后开始糊正解,正解,正解!
思考一个圆覆盖的点集,肯定可以通过某种变换让两个点在圆上且包含的点不变
分类讨论:
1.两个点刚开始就在圆上
2.一个点在圆上,绕该点旋转圆,第一个点碰到边界时停下,转化为1
3.无点在圆上,先平移,第一个点碰到边界时停下,转化为2
于是,上述结论证明了.
/-----------------------------------------------/
那么只需要枚举两个点,因为知道半径,所以就可以算出两个圆心作为候选答案,然后暴力统计答案即可
复杂度n^3
/-----------------------------------------------/
然而,我却没有用这种方法,而是学习了zrf的方法,因为这种方法更好写且复杂度更优,仅为n^2logn,
首先,思考一个圆覆盖的点集,肯定可以通过某种变换让至少一个点在圆上且包含的点不变
这就不证明了,跟上面的证明如出一辙
那么就先枚举这个点
再使用扫描线做法,统计出每个可能被覆盖的点开始被覆盖的弧度和离开圆的弧度,然后排一遍序,一边扫过去统计答案.
如图
然后就非常简单了
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=210; const double pi=3.14159265358979323,eps=1e-12; struct vec{ double x,y; inline vec(double x=0,double y=0):x(x),y(y){}; }a[N]; inline vec operator -(vec x,vec y){ return vec(x.x-y.x,x.y-y.y); } inline double dot(vec x,vec y){ return x.x*y.x+x.y*y.y; } inline int dcm(double x,double y){ return fabs(x-y)<eps?0:x>y?1:-1; } vector<pair<double,int> >g; int n,w[N]; double r; ll ans; ll solve(const int x){ g.clear(); ll res=0,ttt=0; for (int i=1; i<=n; i++) if (i!=x){ vec t=a[i]-a[x]; double dis=sqrt(dot(t,t)),now,oth; if (dcm(dis,2*r)==1) continue; now=atan2(t.y,t.x); oth=acos(dis/(2*r)); g.push_back(make_pair(now-oth,w[i])); g.push_back(make_pair(now+oth,-w[i])); } sort(g.begin(),g.end()); for (vector<pair<double,int> >::iterator it=g.begin(); it!=g.end(); it++){ ttt+=(*it).second; res=max(res,ttt); } return res+w[x]; } int main(){ scanf("%lf%d",&r,&n); for (int i=1; i<=n; i++) scanf("%lf%lf%d",&a[i].x,&a[i].y,&w[i]); for (int i=1; i<=n; i++) ans=max(ans,solve(i)); printf("%lld",ans); }
/------------------------------------------------/
但是还有一个事情没有说,
那就是彩蛋
那就是atan2函数
atan2(double y,double x)是系统自带的,精度比atan要好很多(注意是先y后x)
因为他计算时如果fabs(x)>fabs(y)那么算的时候就会使用atan(y/x)而不是atan(x/y)
然而他又不完全等于atan
他的返回值在(-pi,pi)之间
那么是什么含义呢
那就是原点向右的水平向量作为始边(x轴)
以逆时针为正方向
原点出发的向量(x,y)作为终边的角度值
于是,这玩意是极角排序神器,不仅精度高,而且方便
比如说,你要对n个点进行极角排序,可以不用差积,任意选一个点作为一个原点直接按照atan2值排序
就得到了第三象限->第四象限->第一象限->第二象限的一个序列