zoukankan      html  css  js  c++  java
  • [CSP-S模拟测试]:小P的生成树(数学+Kruskal)

    题目描述

      小$P$是个勤于思考的好孩子,自从学习了最大生成树后,他就一直在想:能否将边权范围从实数推广到复数呢?可是马上小$P$就发现了问题,复数之间的大小关系并没有定义。于是对于任意两个复数$z_1,z_2$,小$P$定义$z_1<z_2$当且仅当$|z_1|<|z_2|$。
      现在,给出一张$n$个点$m$条边的简单无向带权图,小$P$想问你,如果按照他对复数大小的定义,这个图的最大生成树是什么?


    输入格式

      输入的第一行为两个正整数$n$和$m$,分别表示这个无向图的点数和边数。
      接下来$m$行,每行四个整数$u,v,a,b(1leqslant u,vleqslant n,-1000leqslant a,bleqslant 1000)$,表示点$u$与点$v$之间有一条无向边,边权为$a+bi$。


    输出格式

      输出仅有一个实数,它等于所有的最大生成树中所有边权之和的模长。
      实数四舍五入,保留六位小数。


    样例

    样例输入1:

    3 3
    1 2 1 3
    2 3 2 2
    3 1 3 1

    样例输出1:

    5.830952

    样例输入2:

    6 9
    1 2 4 -1
    2 3 4 1
    3 4 -1 -5
    1 5 -4 0
    4 6 1 -6
    2 6 -6 0
    5 6 -7 5
    2 4 7 1
    1 4 -9 -5

    样例输出2:

    27.459060


    数据范围与提示

    样例$1$解释:

    显然,从该图三条边中任取两条便可以构成一棵生成树,这三棵生成树的边权之和分别为$z_1=3+5i,z_2=4+4i,z_3=5+3i$,其中$|z_2|=sqrt{32}<|z_1|=|z_3|=sqrt{34}$。

    数据范围:

    对于$10\%$的数据:$nleqslant 6$
    对于$30\%$的数据:$nleqslant 12$
    对于另外$20\%$的数据:每条边的边权均为实数
    对于$100\%$的数据:$nleqslant 50,mleqslant 200$,给定的无向图至少存在一个生成树

    提示:

    简单无向图的定义为:没有任何重边和自环的无向图。
    若复数$z_1=a_1+b_1i,z_2=a_2+b_2i$,则$z_1+z_2=(a_1+a_2)+(b_1+b_2)i$。
    设复数$z=a+bi$,符号$|z|=sqrt{a^2+b^2}$表示该复数的模长。
    若复数$z=a+bi$满足条件$b=0$,则该复数为实数。


    题解

    假设我们已经知道了生成树中各边求和所得复数的单位方向向量,那么要使得生成树的边权和模长最大,只需各复数在该方向向量上的投影之和最大。于是,我们将每条边对应的复数在该方向向量上的投影作为其新的权值,做一遍最大生成树即可解决。

    但是显然我们不能枚举所有的方向向量。

    但是根据$kruskal$的算法流程我们可以知道:

    生成树的形态只与各边边权的相对大小有关,而与具体权值无关。

    不妨设两条边,其边权分别是$x_1+y_1i$和$x_2+y_2i(y_1 eq y_2)$当两条边对应的复数投影相等时,方向向量$(cos heta,sin heta)$需要满足:$x_1cos heta+y_1sin heta=x_2cos heta+y_2sin heta$,化简后得$ anfrac{x_1-x_2}{y_2-y_1}Rightarrow heta 1=arctanfrac{x_1-x_2}{y_2-y_1}, heta 2=arctanfrac{x_1-x_2}{y_2-y_1}+pi$。而当$y_1=y_2,x_1 eq x_2$时,等式化为$x_1cos heta=x_2cos hetaRightarrow cos heta=0Rightarrow heta 1=frac{pi}{2}, heta 2=frac{3pi}{2}$。

    那么,我们可以枚举每一对边,算出两条边投影相等时计较的分解点,此时这些分解点会把$[-frac{pi}{2},frac{3pi}{2})$的极角分成若干个小区间。由于每条变得投影大小关于极角$ heta$是连续变化的,所以在每个小极角区间内,所有边的投影相对大小关系不变

    于是我们可以对于每个区间任取一个方向向量做最大生成树,最后取最优方案即可。

    需要注意的是因为$arctan$无法求出$frac{pi}{2}$和$frac{3pi}{2}$,所以要插入这两个值。

    时间复杂度:$Theta(m^3log m)$。

    期望得分:$100$分。

    实际得分:$100$分。


    代码时刻

    #include<bits/stdc++.h>
    using namespace std;
    struct rec{int x,y;double a,b,s;}e[201];
    int n,m,cnt;
    int fa[51];
    double ans,theta[40001];
    bool cmp(rec a,rec b){return a.s>b.s;}
    int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
    double kruskal(double x)
    {
    	int res=0;
    	double a=0.0,b=0.0;
    	for(int i=1;i<=m;i++)e[i].s=e[i].a*sin(x)+e[i].b*cos(x);
    	sort(e+1,e+m+1,cmp);
    	for(int i=1;i<=n;i++)fa[i]=i;
    	for(int i=1;i<=m;i++)
    	{
    		int x=find(e[i].x),y=find(e[i].y);
    		if(x==y)continue;
    		fa[x]=y;
    		res++;
    		a+=e[i].a;
    		b+=e[i].b;
    		if(res==n-1)break;
    	}
    	return a*a+b*b;
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++)
    		scanf("%d%d%lf%lf",&e[i].x,&e[i].y,&e[i].a,&e[i].b);
    	for(int i=1;i<=m;i++)
    		for(int j=i+1;j<=m;j++)
    			if(e[i].b==e[j].b)theta[++cnt]=M_PI/2.0;
    			else{theta[++cnt]=atan((e[i].a-e[j].a)/(e[j].b-e[i].b));theta[++cnt]=theta[cnt-1]+M_PI;}
    	theta[++cnt]=-M_PI/2.0;
    	theta[++cnt]=M_PI*3.0/2.0;
    	sort(theta+1,theta+cnt+1);
    	cnt=unique(theta+1,theta+cnt+1)-theta-1;
    	for(int i=1;i<=cnt;i++)ans=max(ans,kruskal(theta[i]));
    	printf("%.6lf",sqrt(ans));
    	return 0;
    }
    

    rp++

  • 相关阅读:
    bzoj 1031: [JSOI2007]字符加密Cipher 後綴數組模板題
    hdu3949 XOR xor高斯消元
    The xor-longest Path
    Contest20140906 反思
    Contest20140906 ProblemC 菲波拉契数制 DP
    Contest20140906 ProblemA dp+线段树优化
    bzoj 1257: [CQOI2007]余数之和sum 数学 && 枚举
    tyvj P1716
    BZOJ 1012 [JSOI2008]最大数maxnumber【线段树】
    Bzoj1083 1083: [SCOI2005]繁忙的都市【MST】
  • 原文地址:https://www.cnblogs.com/wzc521/p/11675211.html
Copyright © 2011-2022 走看看