题面
思路
一个数的约数之和是固定的,而这个约数之和可能是多个数的约数和;
因此如果我们约数之和 → → → 约数连一条边,必然可以形成树的形状(并且题目说了不能出现重复数字);
建好图后,本题就等价于找出一个森林中,直径最长的树,并求出该树的直径;
这样问题就转化成了求树的直径;
接下来是关于约数和部分;
如果我们暴力的去筛每个数的因子,时间复杂度为 O ( n n ) O(n \sqrt{n}) O(nn)
我们可以用埃氏筛的思想,考虑枚举某个数能成为谁的因子;
也就是枚举倍数;
这样时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
当然还有线性筛的方法 O ( n ) O(n) O(n),太复杂了,略…
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 5e4 + 10;
int sum[N];//sum(i)表示i的约数之和是多少
bool not_root[N];
struct Edge{
int next,v,w;
}e[N];
int head[N],cnt,ans;
void add(int u,int v,int w){
++cnt;
e[cnt].v = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
int dfs(int u){
int d1 = 0,d2 = 0;
for(int i = head[u];i;i=e[i].next){
int to = e[i].v;
int d = dfs(to) + e[i].w;
if(d > d1) d2 = d1,d1 = d;
else if(d > d2) d2 = d;
}
ans = max(ans,d1+d2);
return d1;
}
void solve(){
int n;
cin >> n;
for(int i=1;i<n;++i){
//至少2倍,题目要求约数不包含自己
//i*j ≤ n 防止溢出
//j枚举的是倍数
for(int j=2;j<=n/i;++j){
sum[i*j] += i;
}
}
//一个数的约数之和是固定的,而这个约数之和可能是多个数的约数和;
//因此我们可以连一条约数之和-> 约数 呈现出树的形状
//不能出现0,因此从2开始枚举
for(int i=2;i<=n;++i){
if(i > sum[i]){
add(sum[i],i,1);
not_root[i] = 1;
}
}
for(int i=1;i<=n;++i){
if(!not_root[i]){
dfs(i);
}
}
cout << ans << '\n';
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
solve();
return 0;
}