【bzoj 十连测】[noip2016十连测第九场]Problem C: 小P的生成树(kruskal+数学知识)

时间:2022-12-16 22:17:48

【bzoj 十连测】[noip2016十连测第九场]Problem C: 小P的生成树(kruskal+数学知识)
【bzoj 十连测】[noip2016十连测第九场]Problem C: 小P的生成树(kruskal+数学知识)
【bzoj 十连测】[noip2016十连测第九场]Problem C: 小P的生成树(kruskal+数学知识)

【题解】【kruskal】
【因为要使生成树的边长和模长最大,那么只需每个复数在其方向向量的投影最大。so,可以把投影作为权值来做最大生成树。】
【kruskal的理论告诉我们:生成树的大小只与各边的相对边长有关,也就是说,只需考虑各边的大小关系尽量选权值大的边建生成树就行】
【考虑两边权值: x1+yi1x2+yi2y1y2 ,当两边的复数的投影相等时,方向向量需要满足: x1cosθ+y1sinθ=x2cosθ+y2sinθ ,化简后 tanθ1=y2y1x1x2 θ2=arctany2y1x1x2+π ;而当 x1x2y1=y2 时,等式化为 x1cosθ=x2cosθ=>cosθ=0=>θ1=π2θ2=3π2
【枚举每一对边,算出两条边投影相等时极角的分界点,此时会把区间 [π23π2] 分为若干个小区间,由于每条边的投影大小关于极角的变化是连续的,所以在每个小区间里权值的大小关系不发生改变。】
【枚举每个区间,做最大生成树】
[复数处理时,和实数是相似的]

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct node{
int x,y;
double a,b,way;
}d[410];
int fa[110],cnt,n,m;
double ans,f[80010];
int tmp(node p,node q)
{
return p.way>q.way;
}
int find(int x)
{
if(fa[x]==x) return fa[x];
fa[x]=find(fa[x]);
return fa[x];
}
inline double shorter(double x)
{
double cs=cos(x); double sn=sin(x);
for(int i=1;i<=m;++i) d[i].way=cs*d[i].a+sn*d[i].b;
sort(d+1,d+m+1,tmp);
for(int i=1;i<=n;++i)fa[i]=i;
double A=0,B=0;
for(int i=1,cnt=1;i<=m;++i)
{
int l1=find(d[i].x),l2=find(d[i].y);
if(l1!=l2)
{
A+=d[i].a; B+=d[i].b; fa[l1]=l2;
++cnt;
if(cnt==n) return (A*A+B*B);
}
}
}
inline void solve()
{
cnt=0; ans=-1;
for(int i=1;i<m;++i)
for(int j=i+1;j<=m;++j)
{
double x1=d[i].a,y1=d[i].b,x2=d[j].a,y2=d[j].b;
if(y1>y2) swap(x1,x2),swap(y1,y2);
if(y1==y2) {f[++cnt]=M_PI/2; f[++cnt]=-M_PI/2; continue;}
f[++cnt]=atan((x1-x2)/(y2-y1));
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,shorter((f[i]+f[i-1])/2));
if(ans<0) printf("-1\n");
else printf("%.6lf\n",sqrt(ans));
return;
}
int main()
{
freopen("int.txt","r",stdin);
int i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=m;++i)scanf("%d%d%lf%lf",&d[i].x,&d[i].y,&d[i].a,&d[i].b);
solve();
return 0;
}