这道题看起来是求最小生成式(虽然事实的确是这样),但是跟最小生成树的算法没有什么太大的关系,只需要直到最小生成树的基本定义和素数相关的内容即可,最小生成树的定义如下:
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。链接:完整定义与相关介绍
了解了最小生成树的定义之后来解释为什么说还要用到素数相关的知识,在大于1的整数中一个数不是质数就是合数,如果一个数是合数,那么与这个相关数的最小公倍数就是他本身(例如4:2(4的因数)和4的最小公倍数就是4本身; 例如10:5(10的因数)和10的最小公倍数就是10本身……)这样我们就知道与合数相连的边最小就是他本身(与他的因数建立边)。
合数的问题解决了,我们再看质数,一个质数与一个大于等于2的数的最小公倍数是这个数与2的乘积(道理很简单,知道了结论之后自己举几个例子就很容易能想明白),这里注意2这个数。
不太清楚怎样筛素数的同学可以参考 素数的筛选
建立的树大致如下
完整代码如下:
#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 1e7+100;
int vis[maxn];
int prime[maxn];
int cnt = 0;
void init(){
memset(vis,0,sizeof(vis));
for(int i=2;i<=maxn;i++){
if(!vis[i]) prime[cnt++] = i;
for(int j=0;j<cnt && i*prime[j]<=maxn;j++){
vis[i*prime[j]] = 1;
if(i%prime[j]==0) break;
}
}
}
int main(){
init();//预处理,把素数筛出来
int t;
cin >> t;
while(t--){
int n;
cin >> n;
long long ans = 0;
for(int i=2;i<=n;i++){
if(vis[i]==0 && i!=2){//如果这个数是非2的质数,那么就把这个数跟2建立一条边连接起来
ans += 2*i;
}
else if(vis[i]==1) ans+=i;//如果这个数是合数,那么这个数与他的任意一个因数建边即可,权值是他本身
}
cout << ans << endl;
}
return 0;
}