题目链接:http://ybt.ssoier.cn:8088/statusx.php?runidx=11419609
题目:
【题目描述】
如果一个数 x 的约数和 y (不包括他本身)比他本身小,那么 x 可以变成 y ,y也可以变成 x 。例如 4 可以变为 3 ,1 可以变为 7 。限定所有数字变换在不超过 n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。
【输入】
输入一个正整数 n 。
【输出】
输出不断进行数字变换且不出现重复数字的最多变换步数。
【输入样例】
7
【输出样例】
3
【提示】
样例说明
一种方案为 4→3→1→7。
数据范围与提示:
对于 100% 的数据,1≤n≤50000 。
分析:
此题其实剖析清楚题目的具体含以后就可以知道这就是一个建树并求树的直径的题目。
先求出1~n所有数中满足条件的约数的和,如果满足条件的话,则i与 total[i]建一条无向边即可。
最后求出最长路径。(其中实际上还要注意一下,可能会形成深林,未必一定是一颗树)
求1~n 的数 分别的因数 的方法:
1. n * sqrt(n)的方式
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= i / j ; j ++)
{
if(i % j == 0)
{
// j 就是i 的约数
}
}
}
2. n * logn的方式
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j * i<= n ; j++)
{
// i 为 i * j 的约数
}
}
代码实现:
# include <iostream>
# include <cstring>
# include <stdio.h>
using namespace std;
const int N = 50010 , M = 2 * N;
int h[N],e[M],ne[M],w[M],idx;
int total[N];
bool book[N]; // 标记是否为根节点
void add(int a ,int b , int wei)
{
e[idx] = b;
w[idx] = wei;
ne[idx] = h[a];
h[a] = idx++;
}
int cnt;
int dfs(int a , int father)
{
book[a] = true; //代表a被遍历过了
int dist1 = 0;
int d1 = 0;
int d2 = 0;
for(int i = h[a] ; i != - 1; i = ne[i])
{
int j = e[i];
if(j == father)
{
continue;
}
int d = dfs(j,a) + w[i];
dist1 = max(dist1,d);
if(d > d1)
{
d2 = d1;
d1 = d;
}
else if(d > d2)
{
d2 = d;
}
}
cnt = max(cnt,d1 + d2);
return d1;
}
int main()
{
int n;
scanf("%d",&n);
if(n == 1)
{
printf("0\n");
return 0;
}
memset(h,-1,sizeof h);
// 求约数的基本方式:时间复杂度大致为:O(n * sqrt(n))
/*
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= i / j ; j++)
{
if(i % j == 0)
{
if(i != j)
{
total[i] += j;
}
if(i != (i / j) && (i /j) != j)
{
total[i] += i / j;
}
}
}
}
*/
// 使用一个类似筛法的思想,求i是哪些数的约数 , 时间大致为 n * (logn)
for(int i = 1 ; i <= n ; i++) // i为约数
{
for(int j = 2 ; j <= n / i ; j++)
{
total[i * j] += i;
}
}
for(int i = 2 ; i <= n ; i++) // 1不存在满足条件的情况
{
if(total[i] < i)
{
add(i,total[i],1);
add(total[i],i,1);
}
}
// 由于可能会存在多颗树的情况
for(int i = 1 ; i <= n ; i++)
{
if(!book[i])
{
dfs(i,-1);
}
}
printf("%d\n",cnt);
return 0;
}