链接
http://codeforces.com/contest/996/problem/E
题目大意
给你 个向量,长度都小于 ,让你确定每个向量的符号,使得他们最后加起来长度小于
算法雏形
网上都是什么随机化+贪心,太玄学了,虽然也不错,但我还是喜欢非玄学算法。
三个向量分成一组,每个向量取正负就能变成两个向量;六个向量中选出两个向量,最小夹角满足
,假设拥有最小夹角的两个向量为
,那么
,
把这个东西看作以 为自变量的二次函数,求个导或者直接用判别式加韦达定理,可以证明这个东西不大于 (除以 再乘以 )
那就很显然啦,不停的选择前三个向量进行合并,最后剩下两个的时候,两个向量的长度肯定都不小于 ,同样每个向量拆成正负两个,得到夹角 ,再用同样的方法推导出,最后向量的模的最大值为
现在最棘手的问题就在于,如何记录向量的正负?
并查集思想
并查集是树这一点很显然
每次合并两个对象,我就让其中一个对象成为另一个对象的父亲,同时在父亲节点记录最新的向量。
每次给向量乘以
或者
,实则是给这个集合中每个元素乘以
或
,我只需要在父亲节点打一个标记,这个标记不需要下放,只需要在最后我要计算答案的时候,对于每个节点都沿着父亲一路扫上去,所有标记的乘积就是这个点最终个的符号。(记忆化以做到
)
时间复杂度
补充
启发式合并也可以,时间复杂度
代码
#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;
}