一本通
LOJ
如果一个数 x x x 的约数和 y y y (不包括他本身)比他本身小,那么 x x x 可以变成 y y y, y y y 也可以变成 x x x。例如 4 4 4 可以变为 3 3 3, 1 1 1 可以变为 7 7 7。限定所有数字变换在不超过 n n n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。
事实上,刚读题目时我根本没看懂。
但当我们看到首页时,就明白到底怎么做了——
这不就是个树形DP嘛!
重点就在构造这棵树上。
首先,预处理出小于 N N N的每个数的约数和 a [ i ] a[i] a[i],其中 a [ i ] a[i] a[i]表示 i i i的约数和。
再根据题目,可以得出:
对于每个数 i i i,如果 a [ i ] < i a[i]a[i]<i,那么 i i i和 a [ i ] a[i] a[i]可以互相转化,即两点间连一条边, i i i为 a [ i ] a[i] a[i]的一个儿子。
于是,隐藏在题目中的树就构造好了。
接下来就简单了,在树上找树的直径就可以了。
这里有必要引入树的直径的概念:
树的直径,是指树上最长的简单路径。
(好吧,这是我乱写的)
求树的直径有两种方法:
我们设 d [ i ] d[i] d[i]表示以 i i i为根节点的所有子树中,与 i i i距离最远的一个节点的路径。
那么状态转移方程为:
d [ i ] = m a x ( d [ j ] + v a l ( i , j ) ) , j ∈ s o n ( i ) d[i]=max(d[j]+val(i,j)),j∈son(i) d[i]=max(d[j]+val(i,j)),j∈son(i)
表示以子树为根节点的最大路径与当前根节点连边后的最大值即为当前的最大值。
树的直径其实就是左子树中标有颜色的边 + v ( 1 , 3 ) + +v(1,3)+ +v(1,3)+右子树标有颜色的点,也就是 d [ 1 ] + v ( 1 , 3 ) + d [ 3 ] d[1]+v(1,3)+d[3] d[1]+v(1,3)+d[3].
因此在枚举i和儿子j时,直径 D = m a x ( d [ i ] + v a l + d [ j ] ) D=max(d[i]+val+d[j]) D=max(d[i]+val+d[j])
#include
using namespace std;
int n,m,ans=0,f[50039];
struct edge{
int v,w;
};
vector<edge> a[50039];
inline void dp(int x,int fa){
for(int i=0;i<a[x].size();i++){
int son=a[x][i].v;
if(son==fa) continue;
dp(son,x);
ans=max(ans,f[x]+f[son]+a[x][i].w);
f[x]=max(f[x],f[son]+a[x][i].w);
}
return;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
int x,y,v;
scanf("%d%d%d",&x,&y,&v);
a[x].push_back(edge{y,v});
a[y].push_back(edge{x,v});
}
dp(1,0);
printf("%d",ans);
return 0;
}
搜索的做法要好理解一点,大致方法如下:
先从任意一点开始搜索,找到离这个节点最远的点 m m m
再从点 m m m开始搜索,找到离点 m m m最远的点 n n n
那么两点之间的距离即为树的直径。
#include
using namespace std;
int ans,point,n,m;
int v[50039],d[50039];
vector<pair<int,int>> a[50039];
void bfs(int x){
queue<int>q;
memset(v,0,sizeof(v));
memset(d,0,sizeof(d));
q.push(x);
d[x]=0;
v[x]=1;
ans=0;
while(!q.empty()){
int h=q.front();
q.pop();
for(int i=0;i<a[h].size();i++){
int u=a[h][i].first,val=a[h][i].second;
if(!v[u]){
v[u]=1;
d[u]=d[h]+val;
q.push(u);
}
}
}
for(int i=1;i<=n;i++)
if(d[i]>ans&&d[i]!=d[0])
ans=d[i],point=i;
return;
}
int main(){
int x,y,V;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&V);
a[x].push_back(make_pair(y,V));
a[y].push_back(make_pair(x,V));
}
bfs(1);
bfs(point);
printf("%d",ans);
return 0;
}
(这是此题最终AC代码,上面两个只是模板)
#include
using namespace std;
int n,a[50039],d[50039];
vector<int> edge[50039];
inline int dp(int x,int fa){//树形dp求树的直径
int ans=0,l1=0,l2=0;
register int i;
for(i=0;i<edge[x].size();i++){
int v=edge[x][i];
if(v==fa) continue;
ans=max(ans,dp(v,x));
d[x]=max(d[x],d[v]);
if(d[v]>l1){
l2=l1;
l1=d[v]+1;
}
else if(d[v]>l2)
l2=d[v];
}
d[x]++;
return max(ans,l1+l2);
}
int main(){
scanf("%d",&n);
register int i,j;
for(i=1;i<=n;i++){
if(a[i]<i){
edge[i].push_back(a[i]);
edge[a[i]].push_back(i);
}
for(j=i*2;j<=n;j+=i)//预处理每个数的约数和
a[j]+=i;
}
printf("%d",dp(1,0)-1);
return 0;
}