题意:
如果一个数 x
的 约数之和 y
(不包括他本身)比他本身 小,那么 x
和 y
可以 互相转换
给定一个正整数 n
,求出正整数 [1,n]
集合中:在不出现重复数字的情况下,能进行的最大变换步数
思路:
1、如果一个数 x
的约数之和 y
(不包括他本身)比他本身小,则 x
与 y
连一条边,问题转化为求树的最长路径问题
2、由于任意正整数 x
,的 约数之和 是 唯一的,且本题要求只有约数之和 小于 自身才能转换,故对于所有的 x
来说,他向 小于 自己的数转换的边 至多 只有一条,那就是 x
的 约数之和 x′(x′<x)
,但必须从当前树的最小的数(树根)开始向下递归
3、由于题目可能会存在多棵树,因此需要 st[]
数组标记该点是否为根节点,如为根节点则由此向下递归
4、本题由于建的图是有向的,因此无需像之前一样设置第二个参数father
5、在 nlogn
的时间复杂度内预处理出 1~n
中每个数的约数之和
for(int i=1; i<=n; ++i)
{
for(int j=2; j<=n/i; ++j)
{
sum[i*j] += i;
}
}
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4+10;
int n;
int sum[N];
int h[N], e[N], ne[N], w[N], idx;
bool st[N];
int ans;
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int dfs(int u)
{
int down1 = 0, down2 = 0;
for(int i=h[u]; ~i; i=ne[i])
{
int j = e[i];
int d = dfs(j) + w[i];
if(down1<=d) down2 = down1, down1 = d;
else if(down2<d) down2 = d;
}
ans = max(ans, down1 + down2);
return down1;
}
int main()
{
cin>>n;
for(int i=1; i<=n; ++i)
{
for(int j=2; j<=n/i; ++j)
{
sum[i*j] += i; //在 nlogn 的时间复杂度内预处理出 1~n 中每个数的约数之和
}
}
memset(h, -1, sizeof h);
for(int i=2; i<=n; ++i) //不可以从 1 开始建图,1 的约数和为 0,不符合题意
{
if(i>sum[i]) add(sum[i], i, 1), st[i] = true;
}
for(int i=1; i<=n; ++i)
if(!st[i]) dfs(i);
cout<<ans<<endl;
return 0;
}