题意:
如果一个数 x 的约数之和 y(不包括他本身)比他本身小,那么 x 可以变成 y,y 也可以变成 x。
例如,4 可以变为 3,1 可以变为 7。
限定所有数字变换在不超过 n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。
思路:
可以将每个数与能到达的数之间连一条边,这样就会形成一个森林,而题目要求的就是在森林中找一棵树的最大直径。
问题转换为求树的最大直径:
第一步:用筛法的变形求每个数的约数之和
第二部:(从满足条件的树根开始遍历)dfs求数的最长路径
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=50010,M=2*N;
int h[N],e[N],ne[M],idx;
int sum[N];
bool st[N]; //记录树根
int n;
int ans;
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int dfs(int u)
{
int d1=0,d2=0;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
int d=dfs(j)+1;
if(d>=d1) d2=d1,d1=d;
else if(d>d2) d2=d;
}
ans=max(ans,d1+d2);
return d1;
}
int main()
{
cin>>n;
memset(h,-1,sizeof h);
//求每个数的因数的和
for(int i=1;i<=n;i++)
for(int j=2;j<=n/i;j++)
sum[j*i]+=i;
//将满足条件的建立一条边,查找的时候从跟向下查,只需要建立单边
for(int i=2;i<=n;i++)
if(i>sum[i])
{
add(sum[i],i);
st[i]=true;
}
//遍历树根
for(int i=1;i<=n;i++)
if(!st[i])
dfs(i);
cout<<ans<<endl;
return 0;
}