题目描述
小`P`时勤于思考的好孩子,自从学习了最大生成树后,他就一直想:能否将边权范围从实数推广到复数呢?可是马上小`P`就发现了问题,复数之间的大小关系并没有定义。于是对于任意两个复数 $z_1,z_2$ ,小`P`定义 $z_1<z_2$ 当且仅当 $|z_1|<|z_2|$ 。
现在,给出一张 $n$ 个点 $m$ 条边的简单无向带权图,小`P`想问你,如果按照他对复数大小的定义,这个图的最大生成树是什么?
题解
好像在杭州的时候讲过这题,答案是一个复数,放在复平面上的话是个向量,所以如果确定方向的话我们就是把所有向量在这个平面上做投影,要使得投影的和最大去做最大生成树。
可惜角度非常多,那我们可以做最大生成树的时候我们只需要确定两条边的大小关系,不需要只要他具体的值。所以我们可以考虑到两条向量相等的时候角度是确定的,所以我们把这些角度记录下来后排序,然后对于两角直接的方向边的大小关系都是一样的,所以任选一个方向去投影,做最大生成树即可。
效率: $O(m^3logm)$ (有排序)。
代码
#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 ",sqrt(ans));return 0; }