第八届广西大学生程序设计大赛暨2025邀请赛 L题思路分享(数论,最小生成树)

https://ac.nowcoder.com/acm/contest/110811/L

题意概述

给定一张 \(n\) 个点的完全图,每个点有点权 \(a_i\)\(u\)-\(v\) 的边权为 \(gcd(a_i+a_j)+lcm(a_i+a_j)\),求图的最小生成树的边权和。

\(1\le n \le 10^5\)\(1 \le a_i \le 10^6\)

思路

考虑枚举最大公因数 \(d\),找到所有点权为 \(d\) 的倍数的点。在所有满足条件的点中,将所有点与点权最小的点连边即可。

记值域为 \(V\),时间复杂度和边的数量都是调和级数级别,连完边后跑 \(kruskal\) 即可。

时间复杂度 \(\mathcal{O}(V\log V)\)

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

class dsu{
public:
	int n,cnt_cc;
	vector<int> p,sz;
	
	dsu(int _n){
		n = _n;
		cnt_cc = n;
		p = vector<int>(n+1);
		for (int i=1;i<=n;i++){
			p[i] = i;
		}
		
		sz = vector<int>(n+1,1);
	}
	
	int find(int x){
		int root = x;
		while (p[root]!=root) root = p[root];
		
		while (x!=root){
			int next = p[x];
			p[x] = root;
			x = next;
		}
		
		return root;
	}
	
	void unite(int a,int b){
		a = find(a);
		b = find(b);
		
		if (a==b) return;
		
		if (sz[a]>=sz[b]){
			sz[a] += sz[b];
			p[b] = a;
		}
		else{
			sz[b] += sz[a];
			p[a] = b;
		}
		
		cnt_cc--;
	}
};

const int MAXN = 1e6+5;
vector<int> V[MAXN];

void solve(){
	int n;
	cin >> n;

	vector<int> a(n+1);
	for (int i=1;i<=n;i++){
		cin >> a[i];
		V[a[i]].push_back(i);
	}

	vector<array<ll,3>> eds;
	for (int d=1;d<MAXN;d++){
		int x = d;
		while (x<MAXN && V[x].empty()){
			x += d;
		}

		int first = V[x].front();
		while (x<MAXN){
			for (auto& v:V[x]){
				if (v!=first){
					eds.push_back({d+(ll)x*a[first]/d,first,v});
				}
			}
			x += d;
		}
	}

	sort(eds.begin(),eds.end());
	dsu ds(n);
	ll res = 0;

	for (auto& [w,u,v]:eds){
		if (ds.find(u)!=ds.find(v)){
			res += w;
			ds.unite(u,v);
		}
	}

	cout << res << '\n';
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	// cin >> t;
	while (t--) solve();

	return 0;
}
posted @ 2026-05-28 18:36  kzssCCC  阅读(12)  评论(0)    收藏  举报