题目描述
小P
时勤于思考的好孩子,自从学习了最大生成树后,他就一直想:能否将边权范围从实数推广到复数呢?可是马上小P
就发现了问题,复数之间的大小关系并没有定义。于是对于任意两个复数
z
1
,
z
2
z_1,z_2
z1,z2 ,小P
定义
z
1
<
z
2
z_1<z_2
z1<z2 当且仅当
∣
z
1
∣
<
∣
z
2
∣
|z_1|<|z_2|
∣z1∣<∣z2∣ 。
现在,给出一张
n
n
n 个点
m
m
m 条边的简单无向带权图,小P
想问你,如果按照他对复数大小的定义,这个图的最大生成树是什么?
题解
好像在杭州的时候讲过这题,答案是一个复数,放在复平面上的话是个向量,所以如果确定方向的话我们就是把所有向量在这个平面上做投影,要使得投影的和最大去做最大生成树。
可惜角度非常多,那我们可以做最大生成树的时候我们只需要确定两条边的大小关系,不需要只要他具体的值。所以我们可以考虑到两条向量相等的时候角度是确定的,所以我们把这些角度记录下来后排序,然后对于两角直接的方向边的大小关系都是一样的,所以任选一个方向去投影,做最大生成树即可。
效率: O ( m 3 l o g m ) O(m^3logm) O(m3logm) (有排序)。
代码
#include <bits/stdc++.h>
#define db double
using namespace std;
const db p=acos(-1);
int n,m,t,u[205],v[205],fa[55];
db a[205],b[205],f[40005],ans;
struct O{int x,y;db i,j,z;}g[205];
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
bool cmp(O A,O B){return A.z>B.z;}
db work(db w){
db X=sin(w),Y=cos(w),U=0,V=0;
for (int i=1;i<=m;i++)
g[i]=(O){u[i],v[i],a[i],b[i],b[i]*X+a[i]*Y};
sort(g+1,g+m+1,cmp);
for (int i=1;i<=n;i++) fa[i]=i;
for (int x,y,i=1;i<=m;i++){
x=get(g[i].x);y=get(g[i].y);
if (x!=y) fa[x]=y,U+=g[i].i,V+=g[i].j;
}
return U*U+V*V;
}
int main(){
cin>>n>>m;
for (int i=1;i<=m;i++)
scanf("%d%d%lf%lf",&u[i],&v[i],&a[i],&b[i]);
for (int i=1;i<m;i++)
for (int j=i+1;j<=m;j++){
if (b[i]==b[j]) f[++t]=-p/2;
else f[++t]=atan((a[i]-a[j])/(b[i]-b[j]));
f[t+1]=f[t]+p;t++;
}
f[++t]=-p/2;f[++t]=p*5/2;
sort(f+1,f+t+1);t=unique(f+1,f+t+1)-f-1;
for (int i=1;i<=t;i++)
ans=max(ans,work((f[i]+f[i-1])/2));
printf("%.6lf\n",sqrt(ans));return 0;
}