Divide by Zero 2021 and Codeforces Round #714 (Div. 2) D. GCD and MST

题意

在一个长度为n的数组之间建立一个最小生成树,默认相邻两点间的距离是p。如果两个点i,j(i < j)之间满足 gcd(ai,ai+1,…aj) = min(ai,ai+1,…aj)。则ai到aj 之间有一条长度为gcd(ai,ai+1,…aj)的路,求最小生成树的总长度。

思路

满足最大长度的 gcd(ai,ai+1,…aj) = min(ai,ai+1,…aj) 这个值如果为ak 那么i,j之间每个点(除了k)到k都存在一条ak的边,且他们都是ak的倍数。且ai-1,aj+1一定不是ak的倍数。

一般最小生成树使用克鲁斯卡尔算法,从最短边开始枚举,按照这种思路。从最小的ai,枚举到最大的小于p的边,依次判断两边是否为ai的倍数,如果是则存在这条边,在枚举的时候用并查集判断联通。因为每次枚举都是一个区间,且边从小到大枚举是不存在下一个区间嵌套以前的一个区间的情况(因为前面的值至少有一个小于后面的值,所以肯定前面的值不可能是后面值的倍数),所以在遇到在一个集合里面,可以直接退出循环,不需要在向前枚举,可以使枚举变为线性。

代码

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
#define int long long
const int N = 2e5 + 10;
int p[N],id[N];
int a[N];
int find(int x){
	if(x != p[x]) p[x] = find(p[x]);
	return p[x];
}
bool cmp(int x,int y){
	return a[x] < a[y];
}
signed main(){
	int T;
	cin >> T;
	while(T--){
		int n,k;
		scanf("%lld%lld",&n,&k);
		for(int i = 1;i <= n;i++){
			scanf("%lld",&a[i]);
			p[i] = id[i] = i;
		}
		sort(id + 1,id + 1 + n,cmp);
		int ans = 0;
		for(int i = 1;i <= n;i++){
			int u = id[i];
			int v = a[u];
			int x = u - 1;
			if(v >= k) break;
			while(x > 0){
				if(a[x] % v == 0){
					int pa = find(x),pb = find(u);
					if(pa != pb) p[pa] = pb,ans += v;
					else break;
				}
				else break;
				x--;
			}
			x = u + 1;
			while(x <= n){
				if(a[x] % v == 0){
					int pa = find(x),pb = find(u);
					if(pa != pb) p[pa] = pb,ans += v;
					else break;
				}
				else break;
				x++;
			}
		}
		for(int i = 2;i <= n;i++){
			int pa = find(i - 1),pb = find(i);
			if(pa != pb) p[pa] = pb,ans += k;
		}
		printf("%lld\n",ans);
	}
	
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值