题意:给你 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an和 b 1 , b 2 , . . . , b n b_1,b_2,...,b_n b1,b2,...,bn,让你找出最大的 ( ∑ i = 1 k a c i ) 2 + ( ∑ i = 1 k b c i ) 2 (\sum_{i=1}^k a_{c_i})^2+(\sum_{i=1}^k b_{c_i})^2 (∑i=1kaci)2+(∑i=1kbci)2。其中 { c i } ⊂ { 1 , 2 , . . . , n } \set{c_i} \subset \set{1,2,...,n} {ci}⊂{1,2,...,n}。因为结果很大,所以只要相对误差不超过 1 0 − 9 10^{-9} 10−9即算通过。
我一开始尝试用贪心的思路,发现根本行不通。
然后根据题目的提示,感觉可以转化成一个平面图来做,也就是选择若干个向量使得它们的和的模最大。
然后想象,如果确定一个方向,那么只需要把这个方向 9 0 0 90^0 900以内的向量全部做垂线到这个方向上(并且称这些向量为这个方向的集合),然后全部加起来即可。显然,答案即使和最大的方向。
容易发现,如果把相同集合的方向合并, 那么总数是 O ( n ) O(n) O(n)的,对于一个集合并不需要去考虑具体方向的值,只要把向量全部加起来即可。
于是,我们把所有的向量挪到原点,并按照极角排序,然后将一条过原点的直线旋转,并不断统计直线一侧的向量之和,答案即使最大值。
后面瞄了一眼题解,发现用的是一种叫做闵可夫斯基凸包的做法,但从具体做法来讲和我的差不多。
#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define dwn(i,x,y) for(int i=x;i>=y;i--)
#define ll long long
using namespace std;
template<typename T>inline void qr(T &x){
x=0;int f=0;char s=getchar();
while(!isdigit(s))f|=s=='-',s=getchar();
while(isdigit(s))x=x*10+s-48,s=getchar();
x=f?-x:x;
}
int cc=0,buf[31];
template<typename T>inline void qw(T x){
if(x<0)putchar('-'),x=-x;
do{buf[++cc]=int(x%10);x/=10;}while(x);
while(cc)putchar(buf[cc--]+'0');
}
const int N=6e5+10;
const double pi=acos(-1.0),eps=1e-10;
struct node{
ll x,y;double z;
}a[N];int n;
bool cmp(node p1,node p2){
return p1.z<p2.z;
}
void solve(){
qr(n);
rep(i,1,n){
ll x,y;
qr(x),qr(y);
a[i].x=x-y;
qr(x),qr(y);
a[i].y=x-y;
a[i].z=atan2(a[i].y,a[i].x);
if(a[i].z<0)a[i].z+=2.0*pi;
}
sort(a+1,a+n+1,cmp);
rep(i,1,n){
a[i+n]=a[i];
a[i+n].z+=2.0*pi;
}
double liml=0,limr=pi;
int l=1,r=0;
long long sumx=0,sumy=0;
double ans=0.0;
while(liml<2.0*pi){
while(r<2*n&&a[r+1].z<limr-eps){
r++;
sumx+=a[r].x,sumy+=a[r].y;
}
while(l<2*n&&a[l].z<liml+eps){
sumx-=a[l].x,sumy-=a[l].y;
l++;
}
ans=max(ans,1.0*sumx*sumx+1.0*sumy*sumy);
double disl=a[l].z-liml;
double disr=r==2*n?1000:a[r+1].z-limr;
liml+=min(disl,disr)+2.0*eps;
limr=liml+pi;
}
cout<<fixed<<setprecision(10)<<ans<<endl;
}
int main(){
int tt;tt=1;
while(tt--)solve();
return 0;
}