题解:最大生成树+数论。
因为我们最终是要求 sqrt(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 此时的角度为pi/2,3*pi/2
我们枚举每一对边,计算出两边投影相等时极角的分界点,此时这些分界点会把[-pi/2,3*pi/2)的极角区间分成若干个小区间。由于每条边的投影的大小是关于极角连续变化的,所以在每个小极角区间内,所有边的投影相对大小关系不变。
于是枚举区间,取区间任意方向做最大生成树即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 400000
using namespace std;
double f[N],a[N],b[N],ans;
int fa[N],u[N],v[N],n,m,cnt;
struct data{
int x,y;
double v,a,b;
}tr[N];
int cmp(data a,data b)
{
return a.v>b.v;
}
int find(int x)
{
if (fa[x]==x) return x;
fa[x]=find(fa[x]);
return fa[x];
}
double work(double x)
{
double x1=sin(x); double y1=cos(x);
for (int i=1;i<=m;i++) {
tr[i].x=u[i]; tr[i].y=v[i]; tr[i].a=a[i]; tr[i].b=b[i];
tr[i].v=y1*a[i]+x1*b[i];
}
sort(tr+1,tr+m+1,cmp);
for (int i=1;i<=n;i++) fa[i]=i;
double ansa=0,ansb=0; int size=0;
for (int i=1;i<=m;i++)
{
int r1=find(tr[i].x); int r2=find(tr[i].y);
if (r1!=r2) {
fa[r2]=r1;
size++; ansa+=tr[i].a; ansb+=tr[i].b;
if (size==n-1) break;
}
}
return ansa*ansa+ansb*ansb;
}
void solve()
{
cnt=0; ans=-1;
for (int i=1;i<=m-1;i++)
for (int j=i+1;j<=m;j++)
{
if (b[i]==b[j]) {
f[++cnt]=M_PI/2; f[++cnt]=-M_PI/2;
}
else f[++cnt]=atan((a[i]-a[j])/(b[j]-b[i])),f[cnt+1]=f[cnt]+M_PI,cnt++;
}
f[++cnt]=-M_PI/2; f[++cnt]=M_PI*5/2;
sort(f+1,f+cnt+1);
cnt=unique(f+1,f+cnt+1)-f-1;
for (int i=1;i<=cnt;i++)
ans=max(ans,work((f[i]+f[i-1])/2));
printf("%.6lf\n",sqrt(ans));
}
int main()
{
freopen("mst.in","r",stdin);
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) scanf("%d%d%lf%lf",&u[i],&v[i],&a[i],&b[i]);
solve();
}