树形DP 树的最长路径、树的中心

1072. 树的最长路径
1、树的最长路径:树的最长路径又叫树的直径,即这棵树中距离最远的两个结点的距离。
2、找树的直径的做法:
①任取1点作为起点,找到距离该点最远的一个点u。
②再找到距离点u最远的点v。
③u和v之间的路径就是树的一个直径。

思路:对每个结点,把它能往下走的所有路径枚举一遍,记下最长路径d1和次长路径d2,所以经过这个点的总最长路径长度为=d1+d2。

#include<iostream>
#include<cstring>
using namespace std;
const int N=1e4+10;
int w[N*2],e[N*2],h[N*2],ne[N*2],idx;
int ans;
void add(int a,int b,int c)
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u,int father)//这里设置fathe是防止从父节点下来又走到父节点,死循环
{
	int d1=0,d2=0;//d1,d2分别记录该点往下走的最长距离和次长距离
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		int d=dfs(j,u)+w[i];
		if(d>=d1) d2=d1,d1=d;//更新最长距离和次长距离
		else if(d>d2) d2=d;
	}
	ans=max(ans,d1+d2);
	return d1;
}
int main()
{
	int n;
    memset(h,-1,sizeof(h));
	scanf("%d",&n);
	int a,b,c;
	for(int i=0;i<n-1;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c),add(b,a,c);
	}
	dfs(1,-1);
	printf("%d\n",ans);
	return 0;
}

1073. 树的中心
题意:给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。
请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。

思路:选定一个点,对于该点,它可以往下走(子节点),也可以往上走(父节点)。
①往下走:找出距离当前该点最远的距离d1
②往上走:对于它的父节点,又有两个选择,向上,向下,向下的时候,因为不能又走回来到这个父节点的子节点j,所以要特判一下如果当前向下走的最长路径经过j,那么我们不走这条最长的路径,走次长的路径d2。

与树的直径的区别:他们的思路是一致的,只是树的中心需要把所有点的最长路径和次长路径都计算出来,而树的直径只需要任意选一个点进行计算即可,所以树的中心需要进行两趟dfs,分别是用子节点来更新父节点,用父节点来更新子节点。

#include<iostream>
#include<cstring>
using namespace std;
const int N=1e4+10,INF=0x3f3f3f3f;
int e[N*2],w[N*2],ne[N*2],h[N],idx;
int d1[N],d2[N];//分别存储该节点往下走的最大值和次大值,这两个一定不会经过同一条路径
int up[N],p[N];
int n;
void add(int a,int b,int c)
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dfs_d(int u,int father)
{
	d1[u]=d2[u]=-INF;
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		int d=dfs_d(j,u)+w[i];
		if(d>d1[u]) 
		{
			d2[u]=d1[u],d1[u]=d;
			p[u]=j;//记录这个结点的最长路径是经过哪个点
		}
		else if(d>d2[u])
			d2[u]=d;
	}
	if(d1[u]==-INF)//u是叶子节点
		d1[u]=d2[u]=0;
	return d1[u];
}
int dfs_up(int u,int father)
{
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		if(p[u]==j) //如果父节点向下走的最长的一条路径经过j,则不能选这条路,要换次长的
			up[j]=max(up[u],d2[u])+w[i];
		else
			up[j]=max(up[u],d1[u])+w[i];
		dfs_up(j,u);
	}
}
int main()
{
    memset(h,-1,sizeof(h));
	scanf("%d",&n);
	int a,b,c;
	for(int i=0;i<n-1;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c),add(b,a,c);
	}
	dfs_d(1,-1);
	dfs_up(1,-1);
	int res=INF;
	for(int i=1;i<=n;i++)
    	res=min(res,max(d1[i],up[i]));
	printf("%d\n",res);
	return 0;
}

1075. 数字转换

题意:
思路:每一个数的约数之和一定是确定的,并且是有且只有一个,所以这个数和它的
约数之和是可以有一条边的,所以是可以构成很多棵树,形成一个森林的。
这道题目就变成了找出树中一条最长的路径。

这里注意在求约数之和的时候,不要直接暴力求每个数的约数之和是多少,换一个角度,约数之和是j的数是谁,降低时间复杂度。 

#include<iostream>
#include<cstring>
using namespace std;
const int N=5e4+10;
int h[N],e[N],ne[N],idx;
int sum[N];
bool st[N];
int n,ans;
void add(int a,int b)
{
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u,int father)
{
	st[u]=true;
	int d1=0,d2=0;
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		int d=dfs(j,u)+1;
		if(d>d1)
			d2=d1,d1=d;
		else if(d>d2)
			d2=d;
	}
	ans=max(ans,d1+d2);
	return d1;
}
int main()
{
	scanf("%d",&n);
	memset(h,-1,sizeof(h));
	for(int i=1;i<=n;i++)
		for(int j=2;j<=n/i;j++)
			sum[i*j]+=i;//记录i*j的约数之和
	for(int i=2;i<=n;i++)
		if(i>sum[i])
			add(sum[i],i);
	for(int i=1;i<=n;i++)
		if(!st[i])//可能有多棵树
			dfs(i,-1);
	printf("%d\n",ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值