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

102 篇文章 1 订阅



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

因为我们最终是要求 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 400000
using 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值