题目描述:
有一个n个节点的完全图,编号从1到n。对于点i和点j(i<j),如果j−i≤k,那么i和j之间有一条边权为lcm(i,j)的边,否则有一条边权为gcd(i,j)的边。
求该完全图的最小生成树。
输入:
n,k(1<=n,k<=2e5)
输出:
输出最小生成树边权和
Solution:
首先我们可以由题意简单地发现,[k+2,n]中的点只要与1连gcd边便能使边权最小,所以我们只需要考虑[2,k+1]中的点应该如何连边。
假设[2,k+1]中的某个点为a
如果要使点a所连边最小,那必然是和[a+k+1,n]中存在一个点b使gcd(a,b)=1,而任何数与素数的gcd都为1,故此时,我们在[k+2,n]的区间中只要从后往前枚举数p是否为素数同时判断u=p-k-1是否是[2,k+2]中的点,如果是则[p,n]中的点能与u作gcd边,而如果此时不存在素数我们便暴力枚举[p,n]中的点与u点的gcd边最小值,如果存在素数s,则之后[k+2,p-1]所能在[2,1+k]中影响到的点所连的边必为1。
而对于[2,k+1]中只能连lcm的点,我们只需与点1连lcm边即为最小值。
C++code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, k, cnt = 0;
cin >> n >> k;
ll res = 0;
map<int, bool>st;
vector<int>c;
int has_pr = 1;//判断是否找到素数
for (int i = n; i >= k + 2; i--) {//[k+2,n]是与1建边的点,在这个区间中从后往前枚举[1,k+1]中能与[k+2,n]作gcd边的点
res++;
if (has_pr) {
int flag = 0;
for (ll j = 2; j * j <= i; j++) {//判断改点是否为素数,如果不是存入c中
if (i % j == 0) {
flag = 1;
break;
}
}
if (!flag)has_pr = 0;
else c.push_back(i);
}
int u = i - k - 1;
if (1 < u && u <= 1 + k) {//计算[1,k]能与[k+1,n]建gcd边的最小值
if (has_pr) {
int ans = 1e6;
for (auto sp : c) ans = min(ans, gcd(u, sp));
res += ans;
}
else res += 1;
st[u] = true;
}
}
for (int i = 2; i <= min(n, 1 + k); i++) {//计算[1,k+1]中不受[k+1,n]影响的,与1连lcm边
if (!st[i])res += i;
}
cout << res << endl;
return 0;
}