CF1841F Monocarp and a Strategic Game

题意:给你 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} 109即算通过。

我一开始尝试用贪心的思路,发现根本行不通。

然后根据题目的提示,感觉可以转化成一个平面图来做,也就是选择若干个向量使得它们的和的模最大。

然后想象,如果确定一个方向,那么只需要把这个方向 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;
}
  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值