题目描述
题解
因为我们最终是要求
sum(a)2+sum(b)2−−−−−−−−−−−−−−−√
尽可能的大,所以我们肯定不能单独考虑其中一个权值的影响。那么如何将两个影响考虑到一起呢?我们把
(ai,bi)
看成是有方向的向量,那么最终选取的边的和应该也是一个有方向的向量,假设我们找出该向量的极角,然后求出每个边对于该方向的投影,投影越大说明对该方向的贡献越大,所以我们可以根据
a∗cos+b∗sin
的值排序,然后用kruskal计算此方向的贡献。
那么角度有很多肯定不能全部枚举。根据kruskal的流程可知,生成树的形态与各边权值的相对大小有关,与具体权值无关。
我们考虑两条边的边权
x1+y1i
与
x2+y2i
(y1!=y2)
当两条边对应复数的投影相等时,方向向量
(cos,sin)
需要满足:
x1∗cos+y1∗sin=x2∗cos+y2∗sin
,化简后
tan=(x1−x2)(y2−y1)
,然后用
atan
可以解出其中的一个角,
atan+pi
可以得到另一个。而当
y1=y2,x1!=x2,
式子会化简成
x1∗cos=x2∗cos
此时的角度为
[pi2,32∗pi]
我们枚举每一对边,计算出两边投影相等时极角的分界点,此时这些分界点会把
[−pi2,32∗pi]
的极角区间分成若干个小区间。由于每条边的投影的大小是关于极角连续变化的,所以在每个小极角区间内,所有边的投影相对大小关系不变。
于是枚举区间,取区间任意方向做最大生成树即可。
代码
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 40005
int n,m,fa[N];
struct hp{int u,v;double a,b,s;}e[N];
double an[N],ans;
int cmp(hp a,hp b)
{
return a.s>b.s;
}
int find(int x)
{
if (x==fa[x]) return x;
fa[x]=find(fa[x]);
return fa[x];
}
double mst(double a)
{
double sn=sin(a),cs=cos(a),ans=0;
for (int i=1;i<=m;++i) e[i].s=e[i].a*cs+e[i].b*sn;
sort(e+1,e+m+1,cmp);int edge=0;double A=0,B=0;
for (int i=1;i<=n;++i) fa[i]=i;
for (int i=1;i<=m;++i)
{
int f1=find(e[i].u),f2=find(e[i].v);
if (f1!=f2) fa[f1]=f2,edge++,A+=e[i].a,B+=e[i].b;
if (edge==n-1) break;
}
return A*A+B*B;
}
void solve()
{
int cnt=0;
for (int i=1;i<=m;++i)
for (int j=i+1;j<=m;++j)
{
double x1=e[i].a,y1=e[i].b,x2=e[j].a,y2=e[j].b;
if (y1==y2) an[++cnt]=-M_PI/2.0,an[++cnt]=M_PI/2.0;
else an[++cnt]=atan((x1-x2)/(y2-y1)),an[++cnt]=an[cnt-1]+M_PI;
}
an[++cnt]=-M_PI/2.0;an[++cnt]=M_PI*3.0/2.0;
sort(an+1,an+cnt+1);
cnt=unique(an+1,an+cnt+1)-an-1;
for (int i=2;i<=cnt;++i) ans=max(ans,mst((an[i-1]+an[i])/2.0));
printf("%0.6lf\n",sqrt(ans));
}
int main()
{
freopen("mst.in","r",stdin);
freopen("mst.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=m;++i) scanf("%d%d%lf%lf",&e[i].u,&e[i].v,&e[i].a,&e[i].b);
solve();
}