#3409. 小P的生成树(mst)

题目描述

P时勤于思考的好孩子,自从学习了最大生成树后,他就一直想:能否将边权范围从实数推广到复数呢?可是马上小P就发现了问题,复数之间的大小关系并没有定义。于是对于任意两个复数 z 1 , z 2 z_1,z_2 z1,z2 ,小P定义 z 1 < z 2 z_1<z_2 z1<z2 当且仅当 ∣ z 1 ∣ < ∣ z 2 ∣ |z_1|<|z_2| z1<z2

现在,给出一张 n n n 个点 m m m 条边的简单无向带权图,小P想问你,如果按照他对复数大小的定义,这个图的最大生成树是什么?

题解

好像在杭州的时候讲过这题,答案是一个复数,放在复平面上的话是个向量,所以如果确定方向的话我们就是把所有向量在这个平面上做投影,要使得投影的和最大去做最大生成树。

可惜角度非常多,那我们可以做最大生成树的时候我们只需要确定两条边的大小关系,不需要只要他具体的值。所以我们可以考虑到两条向量相等的时候角度是确定的,所以我们把这些角度记录下来后排序,然后对于两角直接的方向边的大小关系都是一样的,所以任选一个方向去投影,做最大生成树即可。

效率: O ( m 3 l o g m ) O(m^3logm) O(m3logm) (有排序)。

代码
#include <bits/stdc++.h>
#define db double
using namespace std;
const db p=acos(-1);
int n,m,t,u[205],v[205],fa[55];
db a[205],b[205],f[40005],ans;
struct O{int x,y;db i,j,z;}g[205];
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
bool cmp(O A,O B){return A.z>B.z;}
db work(db w){
	db X=sin(w),Y=cos(w),U=0,V=0;
	for (int i=1;i<=m;i++)
		g[i]=(O){u[i],v[i],a[i],b[i],b[i]*X+a[i]*Y};
	sort(g+1,g+m+1,cmp);
	for (int i=1;i<=n;i++) fa[i]=i;
	for (int x,y,i=1;i<=m;i++){
		x=get(g[i].x);y=get(g[i].y);
		if (x!=y) fa[x]=y,U+=g[i].i,V+=g[i].j;
	}
	return U*U+V*V;
}
int main(){
	cin>>n>>m;
	for (int i=1;i<=m;i++)
		scanf("%d%d%lf%lf",&u[i],&v[i],&a[i],&b[i]);
	for (int i=1;i<m;i++)
		for (int j=i+1;j<=m;j++){
			if (b[i]==b[j]) f[++t]=-p/2;
			else f[++t]=atan((a[i]-a[j])/(b[i]-b[j]));
			f[t+1]=f[t]+p;t++;
		}
	f[++t]=-p/2;f[++t]=p*5/2;
	sort(f+1,f+t+1);t=unique(f+1,f+t+1)-f-1;
	for (int i=1;i<=t;i++)
		ans=max(ans,work((f[i]+f[i-1])/2));
	printf("%.6lf\n",sqrt(ans));return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值