2022杭电多校八 1007-Darnassus(kruscal)

题目链接:杭电多校8 - Virtual Judge

题目:

 样例输入:

2
5
4 3 5 1 2
10
4 7 3 8 6 1 9 10 5 2

样例输出:

7
24

题意:给出一个排列p,把每个位置视为点,建一个无向图,i,j之间的边权为|i-j|*|pi-pj|。求这个图的最小生成树。

先来给出最小生成树的一些性质:
(1)一个图的最小生成树不一定唯一,但是其对应第k大边权一定是唯一的

(2)最小生成树的最大边权一定是所有生成树的最大边权的最小值

分析:首先我们尝试着把相邻的点之间连一条边,这样每两个相邻点之间的边权都是|pi-pj|<n,那么我们就可以知道有一棵生成树的边的最大值是小于n的,那么就等价于我们可以构造一棵最小生成树而其中所有边权均小于n,因为最小生成树的最大边权一定是所有生成树的最大边权的最小值。所以最小生成树中所有边权均小于n。那么也就是说我们只需要把所有边权小于n的边记录一下然后跑最小生成树就行。现在问题是我怎么找到边权小于n的边,根据|i-j|*|pi-pj|<n我们可以知道|i-j|和|pi-pj|至少有一个小于sqrt(n),那么我们就可以用mp数组记录点权pi对应的位置i,然后通过2n*sqrt(n)的复杂度把所有的边权小于n的边全部找到,kruscal跑最小生成树需要先对边按照边权从小到大进行排序,但是快速排序的复杂度是(nlogn)级别的,肯定会超时,所以我们这道题只能用桶排,用链式前向星写一个h[i]代表权值为i的边的初始编号,记录一下每个边权出现的编号,最后直接跑一个最小生成树就可以了。注意在桶排的时候不要用vector,容易超时,而用链式前向星写会比vector快一倍多。

下面是代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=5e4+10;
int fu[N],a[N],mp[N];
int h[N],ne[N*600],idx,u[N*600],v[N*600];
struct node{
	int u,v,w;
}p[N*600];
int find(int x)
{
	if(x!=fu[x]) fu[x]=find(fu[x]);
	return fu[x];
}
void add(int x,int y,int z)
{
	u[idx]=x;
	v[idx]=y;
	ne[idx]=h[z];
	h[z]=idx++;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d",&n);
		idx=0;
		for(int i=1;i<=n;i++)
		{
			fu[i]=i;h[i]=-1;
			scanf("%d",&a[i]);
			mp[a[i]]=i;
		}
		int cnt=0;
		for(int i=1;i<=n;i++)
		{
			int t=(int)sqrt(n);
			int e=min(i+t,n);
			for(int j=i+1;j<=e;j++)
			{
				int tt=1ll*(j-i)*abs(a[j]-a[i]);
				if(tt<n)
					add(i,j,tt);
				tt=1ll*abs(mp[j]-mp[i])*(j-i);
				if(tt<n)
					add(mp[i],mp[j],tt);
			}
		}
		long long ans=0;
		int count=0;
		for(int i=1;i<n;i++)
		{
			for(int j=h[i];j!=-1;j=ne[j])
			{
				int fx=find(u[j]),fy=find(v[j]);
				if(fx==fy) continue;
				fu[fx]=fy;
				count++;ans+=i;
				if(count==n-1) break;
			}
			if(count==n-1) break;
		}
		printf("%lld\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值