test 9 小p的生成树 (最大生成树+数论)

来源:互联网 发布:怎么查看手机淘宝等级 编辑:程序博客网 时间:2024/05/21 22:25



题解:最大生成树+数论。

因为我们最终是要求 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 400000using 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();}


0 0