Codeforces Round #492 (Div. 2)E. Leaving the Bar

本文介绍了一种解决特定向量合并问题的非随机化算法。该算法通过将向量分组并选择最小夹角向量进行合并,确保最终向量总长度不超过给定阈值。文章详细解释了算法原理及实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接

http://codeforces.com/contest/996/problem/E

题目大意

给你 n n 个向量,长度都小于106,让你确定每个向量的符号,使得他们最后加起来长度小于 1.5×106 1.5 × 10 6

算法雏形

网上都是什么随机化+贪心,太玄学了,虽然也不错,但我还是喜欢非玄学算法。
三个向量分成一组,每个向量取正负就能变成两个向量;六个向量中选出两个向量,最小夹角满足 θ[0,60] θ ∈ [ 0 , 60 ∘ ] ,假设拥有最小夹角的两个向量为 x⃗ ,y⃗  x → , y → ,那么 |x⃗ |,|y⃗ |106 | x → | , | y → | ≤ 10 6

|x⃗ y⃗ |=|x⃗ |2+|y⃗ |22|x⃗ ||y⃗ |cosθ | x → − y → | = | x → | 2 + | y → | 2 − 2 | x → | | y → | c o s θ

cosθ[12,1] c o s θ ∈ [ 1 2 , 1 ]

2|x⃗ ||y⃗ |cosθ[2|x⃗ ||y⃗ |,|x⃗ ||y⃗ |] − 2 | x → | | y → | c o s θ ∈ [ − 2 | x → | | y → | , − | x → | | y → | ]

|x⃗ y⃗ |[|x⃗ |2+|y⃗ |22|x⃗ ||y⃗ |,|x⃗ |2+|y⃗ |2|x⃗ ||y⃗ | ] | x → − y → | ∈ [ | x → | 2 + | y → | 2 − 2 | x → | | y → | , | x → | 2 + | y → | 2 − | x → | | y → |   ]

把这个东西看作以 b b 为自变量的二次函数,求个导或者直接用判别式加韦达定理,可以证明这个东西不大于106(除以 |x⃗ ||y⃗ | | x → | | y → | 再乘以 |x⃗ ||y⃗ | | x → | | y → | )
那就很显然啦,不停的选择前三个向量进行合并,最后剩下两个的时候,两个向量的长度肯定都不小于 106 10 6 ,同样每个向量拆成正负两个,得到夹角 α[0,90] α ∈ [ 0 , 90 ∘ ] ,再用同样的方法推导出,最后向量的模的最大值为 2×106<1.5×106 2 × 10 6 < 1.5 × 10 6
现在最棘手的问题就在于,如何记录向量的正负?

并查集思想

并查集是树这一点很显然
每次合并两个对象,我就让其中一个对象成为另一个对象的父亲,同时在父亲节点记录最新的向量。
每次给向量乘以 1 − 1 或者 1 1 ,实则是给这个集合中每个元素乘以1 1 − 1 ,我只需要在父亲节点打一个标记,这个标记不需要下放,只需要在最后我要计算答案的时候,对于每个节点都沿着父亲一路扫上去,所有标记的乘积就是这个点最终个的符号。(记忆化以做到 O(n) O ( n ) )

时间复杂度

O(n) O ( n )

补充

启发式合并也可以,时间复杂度 O(nlogn) O ( n log ⁡ n )

代码

#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#define maxn 100010
#define lim (1e6+1e-8)
using namespace std;
struct vec
{
    int num;
    double x, y;
    double mod(){return sqrt(x*x+y*y);}
    vec operator+(vec v){return (vec){0,x+v.x,y+v.y};}
    vec operator*(int t){return (vec){0,t*x,t*y};}
}v[maxn];
int n, f[maxn], c[maxn], C, tag[maxn];
queue<vec> q;
inline void link(int ca, vec &a, int cb, vec &b)
{
    f[b.num]=a.num;
    tag[a.num]*=ca;
    tag[b.num]*=cb*tag[a.num];
    q.push((vec){a.num,ca*a.x+cb*b.x,ca*a.y+cb*b.y});
}
void init()
{
    int i, j;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        v[i].num=i;
        tag[i]=1;
        f[i]=i;
        scanf("%lf%lf",&v[i].x,&v[i].y);
        q.push(v[i]);
    }
}
void work()
{
    vec v1, v2, v3;
    int c1, c2, c3, flag;
    while(q.size()>2)
    {
        v1=q.front(), q.pop();
        v2=q.front(), q.pop();
        v3=q.front(), q.pop();
        flag=1;
        for(c1=-1;c1<=1 and flag;c1+=2)
            for(c2=-1;c2<=1 and flag;c2+=2)
                for(c3=-1;c3<=1 and flag;c3+=2)
                {
                    if((v1*c1+v2*c2).mod()<lim)link(c1,v1,c2,v2), q.push(v3), flag=0;
                    else if((v1*c1+v3*c3).mod()<lim)link(c1,v1,c3,v3), q.push(v2), flag=0;
                    else if((v2*c2+v3*c3).mod()<lim)link(c2,v2,c3,v3), q.push(v1), flag=0;
                }
    }
    v1=q.front(), q.pop();
    v2=q.front(), q.pop();
    if((v1+v2).mod()<(v1+v2*(-1)).mod())link(1,v1,1,v2);
    else link(1,v1,-1,v2);
}
int calc(int x)
{
    if(c[x])return c[x];
    if(x==f[x])c[x]=tag[x];
    else c[x]=tag[x]*calc(f[x]);
    return c[x];
}
int main()
{
    init();
    if(n==1)
    {
        printf("1");
        return 0;
    }
    work();
    for(int i=1;i<=n;i++)if(!c[i])calc(i);
    for(int i=1;i<=n;i++)printf("%d ",c[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值