题目大意:给出一个数n,表示有n-1个顶点,顶点编号是2~n,任意两个点之间的边权是两个点编号的最小公倍数,让我们将这些点连在一起构成一棵树,使这棵树的边权之和最小,输出这个最小的边权之和。
解题思路:这个题乍一看好像是一个最小生成树的题 ,但是通过分析数据规模发现,如果用最小生成树算法必然会T,而且就算只遍历所有点一遍都会T,所以得出初步结论,这个题大致是个找规律的题,最后得到的规律如下:
如果一个点的编号是质数,那么答案需要加上这个质数乘2,如果一个点的编号不是质数,那么答案需要加上这个数,所以最终的答案是所有非质数的和+所有质数的和*2=所有数的和(除去2)+所有质数的和(除去2),所有数的和非常简单一个公式就可以算出来了,至于所有质数的和,我们首先可以预处理出所有数据范围内的质数,并记录在数组prime中,开另一个数组s用来记录prime数组的前缀和,每当给出一个数n,我们都需要在prime数组中找到小于等于n的最大的数的位置pos,根据这个位置就可以得到所有小于n的素数之和s[pos],这样答案就可以通过简单的加减得到了。
上代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <map>
#include <cmath>
using namespace std;
map<long long,int>mp;
const int N=1E7+10;
int index;
int Mark[N],prime[N];
long long s[N];
void Prime(){
for(int i = 2; i < N; i++){
//如果未标记则得到一个素数
if(Mark[i] == 0) prime[++index] = i;
//标记目前得到的素数的i倍为非素数
for(int j = 1; j <= index && prime[j] * i < N; j++){
Mark[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
for(int i=1;i<N;i++)
s[i]=s[i-1]+prime[i];
}
int main() {
int t;
cin>>t;
Prime();
while(t--)
{
long long n;
cin>>n;
long long l=1,r=index,pos;
while(l<r)
{
long long mid=l+r+1>>1;
if(prime[mid]>n)
r=mid-1;
else
l=mid;
}
long long sum=n*(n+1)/2-3;
long long ans=sum+s[l]-2;
if(n==2)
cout<<0<<endl;
else
cout<<ans<<endl;
}
return 0;
}