[BZOJbegin][NOIP十连测第九场]小P的生成树(数学相关+kruskal)

78 篇文章 0 订阅
41 篇文章 0 订阅

题目描述

这里写图片描述
这里写图片描述

题解

因为我们最终是要求 sum(a)2+sum(b)2 尽可能的大,所以我们肯定不能单独考虑其中一个权值的影响。那么如何将两个影响考虑到一起呢?我们把 (ai,bi) 看成是有方向的向量,那么最终选取的边的和应该也是一个有方向的向量,假设我们找出该向量的极角,然后求出每个边对于该方向的投影,投影越大说明对该方向的贡献越大,所以我们可以根据 acos+bsin 的值排序,然后用kruskal计算此方向的贡献。
那么角度有很多肯定不能全部枚举。根据kruskal的流程可知,生成树的形态与各边权值的相对大小有关,与具体权值无关。
我们考虑两条边的边权 x1+y1i x2+y2i (y1!=y2) 当两条边对应复数的投影相等时,方向向量 (cos,sin) 需要满足: x1cos+y1sin=x2cos+y2sin ,化简后 tan=(x1x2)(y2y1) ,然后用 atan 可以解出其中的一个角, atan+pi 可以得到另一个。而当 y1=y2x1!=x2 式子会化简成 x1cos=x2cos 此时的角度为 [pi2,32pi]
我们枚举每一对边,计算出两边投影相等时极角的分界点,此时这些分界点会把 [pi2,32pi] 的极角区间分成若干个小区间。由于每条边的投影的大小是关于极角连续变化的,所以在每个小极角区间内,所有边的投影相对大小关系不变。
于是枚举区间,取区间任意方向做最大生成树即可。

代码

#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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值