题目大意
- 一个数字n,求n以内的数字之间 “约数和关系” 的最长链;
- 约数和关系:一个数字 x 的约数和为 s[x] ;
题目分析
- 由 约数和关系 可以想到,如果 s[x]<x ,他们之间可以连一条双向边;题目就转换为了一个树上,求直径。
- 直径的定义: 树上最长的链。
- 求树的直径的方法1:用两次dfs来完成
dfs1 从根出发,找到最远的叶子结点 k;
dfs2 以 k 为根,出发,找到离他最远的点 t , k 与 t 之间的距离就是直径。
- 求直径的做法2:dp的思维来实现
搜索的过程中,同时记录最长链与次长链,回溯的时候更新。
解题思路1:两次 dfs 求直径
- 如果 s[x]<x ,他们之间可以连一条双向边;
- dfs1 从根出发,找到最远的叶子结点 k;
- dfs2 以 k 为根,出发,找到离他最远的点 t , k 与 t 之间的距离就是直径。
参考代码1
#include<bits/stdc++.h>
using namespace std;
const int N=500007;
struct node{
int nex,to;
}e[N];
int n,ans,su[N];
int nx,lx;
bool b[N];
int las[N],cnt;
void add(int x,int y){
cnt++;
e[cnt].nex=las[x];
e[cnt].to=y;
las[x]=cnt;
}
void dfs1(int x,int c){
if(c>lx){
lx=c;
nx=x;
}
b[x]=1;
for(int i=las[x];i;i=e[i].nex){
int y=e[i].to;
if(!b[y]) dfs1(y,c+1);
}
b[x]=0;
}
void dfs2(int x,int c){
ans=max(ans,c);
b[x]=1;
for(int i=las[x];i;i=e[i].nex){
int y=e[i].to;
if(!b[y]) dfs2(y,c+1);
}
b[x]=0;
}
int main(){
cin >> n;
for(int i=1;i<=n;i++){
for(int j=2;j*i<=n;j++){
su[j*i]+=i;
}
}
for(int i=2;i<=n;i++){
if(i>su[i]){
add(i,su[i]);
add(su[i],i);
}
}
dfs1(1,0);
dfs2(nx,0);
cout << ans;
return 0;
}
解题思路2: dp 求直径
- 因为题面要求,对于x的约数和 s[x]<x ,所以可以设定 s[x] 为父亲节点, x 为子节点。
- dp的思维来实现
搜索的过程中,同时记录最长链 d1[x] 与次长链 d2[x],回溯的时候更新。
- 因为这是一棵不确定根的树,所以最后扫描一次所有的点,记录最大的 d1[x]+d2[x] 就是答案。
参考代码2
#include<bits/stdc++.h>
using namespace std;
const int N=500007;
int n,ans,su[N];
int d1[N],d2[N];
int main(){
cin >> n;
for(int i=1;i<=n;i++){
for(int j=2;j*i<=n;j++){
su[j*i]+=i;
}
}
for(int y=n;y>=1;y--){
if(su[y]>=y) continue;
int x=su[y];
if(d1[x]<d1[y]+1){
d2[x]=d1[x];
d1[x]=d1[y]+1;
}
else if(d2[x]<d1[y]+1)
d2[x]=d1[y]+1;
}
for(int i=1;i<=n;i++) if(d1[i]+d2[i]>ans) ans=d1[i]+d2[i];
cout << ans;
return 0;
}