P3884 [JLOI2009]二叉树问题

题目

如下图所示的一棵二叉树的深度、宽度及结点间距离分别为:

  • 深度:44
  • 宽度:44
  • 结点 8 和 6 之间的距离:88
  • 结点 7 和 6 之间的距离:33

其中宽度表示二叉树上同一层最多的结点个数,节点 u, vu,v 之间的距离表示从 uu 到 vv 的最短有向路径上向根节点的边数的两倍加上向叶节点的边数。

给定一颗以 1 号结点为根的二叉树,请求出其深度、宽度和两个指定节点 x, yx,y 之间的距离。

 解答

        这一道题分为三个问题,第一个问题是求深度,第二个问题是求最大宽度,第三个问题是求a->b的带权路径长度,这里的权指的是如果是向着跟走的路径权值是2,远离根的路径权值是1,例如8->6,那么8->1的路径是要*2,1->6是不需要的,所以是3*2+2=8,样例也就是这样来的。

        对于第一个问题求深度,这个只要走一遍dfs就可以求出答案。

        对于第二个问题求宽度,只需要算出每一个点的深度,然后统计每个深度的个数,找最大就可以得出,因为深度不会很深,所以造的桶也不会太多。

        主要是第三个问题。第三个问题需要算出路径,考虑使用LCA+倍增算法来求解。

        LCA也就是最近公共祖先,有了根节点才可以计算路径权值的分界点和长度。计算LCA的方法有多种,暴力的方法就是直接先统一到同一深度,然后两个同时向上找,直到相遇就算找到,这里的数据量比较小大约是可以通过的,但是一旦数据量变大就难以满足,因此不能一个一个跳,那么就以2的幂次的长度来跳,因为任何整数可以表示成2的幂次的和,因此先大步跳然后减小步幅是可以到达任何深度的。

        首先先进行预处理,引入f [i] [j] 表示编号为i的点向上跳2^j到达的点,那么f[i][j]=f[ f[i][j-1] ][j-1],这一步可以这样理解:编号为i的点向上跳2^j等同于先跳2^(j-1)到达f[i][j-1]然后再由f[i][j-1]跳2^(j-1),利用这样的一个递推式,跑一遍dfs就可以预处理出所有点向上跳2的幂次到达的点。

void dfs(int to, int from)//to为当前节点,from为父节点
{
	dep[to] = dep[from] + 1;//每个点的深度,也可以进行预处理
	f[to][0] = from;
	for (int i = 1; i <= lg[dep[to]]; i++)
		f[to][i] = f[f[to][i - 1]][i - 1];
	for (int i = last[to]; i; i = e[i].next)
		if (e[i].to != from)
			dfs(e[i].to, to);
}

        然后接下来进行LCA相关的计算。一般如果没有方向的话,传入参数有两个x,y,分别代表了要计算的起点和终点。大致步骤有两步,第一步是先把两个深度统一,第二步两个点同时向上跳,枚举2的幂次从最大到最后0次,也就是从跳很多步到最后跳一步,因为两个点是一定有公共祖先的,如果跳的很大那么可能一下子找到了非常靠上的祖先,它虽然是公共祖先但却不是最近公共祖先,因此我们要保证跳了之后两个点不会位于同一点(因为位于同一点就代表位于某一层公共祖先了,但是我们无法确定这是否是最近公共祖先),不断缩小步幅,一直到最后两者一定是位于最近公共祖先的左右节点,因为只有这样才能满足最后跳一步都不满足两者位于同一点,而最近公共节点就是两个点的父节点

        当然这道题因为有权值,所以求解过程略有改变,首先是统一深度,因为是x->y,所以x到跟是向根,路径权值为2,根到y是原远离,所以分情况讨论,如果是x深度大,那么在提升的过程中,结果要加上路程*2。然后最后找出共公共祖先,计算公祖祖先深度和统一深度的差值,那么路径长度就知道了,因为长两边是一样的,无非就是一边权值是2,那么结果加上一边路径长度乘3就好了。

int cal(int x, int y)
{
	int ans = 0;
	if (dep[x] < dep[y])//y先向上走
	{
		int tmp = dep[y];
		while(dep[x]<dep[y])
		{
			y = f[y][lg[dep[y] - dep[x]] - 1];
		}
		ans += tmp - dep[y];
	}
	else//x向上走,这是朝着根的方向
	{
		int tmp = dep[x];
		while (dep[x] > dep[y])
		{
			x = f[x][lg[dep[x] - dep[y]] - 1];
		}
		//printf("tmp=%d,dep[x]=%d\n", tmp,dep[x]);
		ans += 2 * (tmp - dep[x]);
	}
	//printf("ans=%d\n", ans);
	//接下来找到公共祖先
	if (x == y)return ans;//已经找到了
	int tmp = dep[x];//存储当前的深度,后面方便计算路径长度
	for (int i = lg[dep[x]] - 1;i>=0; i--)
	{
		if (f[x][i] != f[y][i])
		{
			x = f[x][i];
			y = f[y][i];
		}
	}
	//printf("x=%d\n", x);
	//最后的父节点是直接父节点
	ans += (tmp - dep[f[x][0]]) * 3;
	return ans;
}

完整代码

#include<stdio.h>
#include<algorithm>
using namespace std;
#define Max 1000
struct Edge
{
	int to, next;
}e[Max];
int last[Max], cnt;

int lg[Max], dep[Max],f[Max][20], deps[20],maxDep;

void add(int from, int to)
{
	e[++cnt].to = to;
	e[cnt].next = last[from];
	last[from] = cnt;
}

void dfs(int to, int from)
{
	dep[to] = dep[from] + 1;
	deps[dep[to]]++;//统计每一层的个数
	maxDep = max(maxDep, dep[to]);
	f[to][0] = from;
	for (int i = 1; i <= lg[dep[to]]; i++)
		f[to][i] = f[f[to][i - 1]][i - 1];
	for (int i = last[to]; i; i = e[i].next)
	{
		//printf("%d %d\n", e[i].next,e[i].to);
		if (e[i].to != from)
		{
			dfs(e[i].to, to);
		}
	}
}

int cal(int x, int y)
{
	int ans = 0;
	if (dep[x] < dep[y])//y先向上走
	{
		int tmp = dep[y];
		while(dep[x]<dep[y])
		{
			y = f[y][lg[dep[y] - dep[x]] - 1];
		}
		ans += tmp - dep[y];
	}
	else//x向上走,这是朝着根的方向
	{
		int tmp = dep[x];
		while (dep[x] > dep[y])
		{
			x = f[x][lg[dep[x] - dep[y]] - 1];
		}
		//printf("tmp=%d,dep[x]=%d\n", tmp,dep[x]);
		ans += 2 * (tmp - dep[x]);
	}
	//printf("ans=%d\n", ans);
	//接下来找到公共祖先
	if (x == y)return ans;//已经找到了
	int tmp = dep[x];//存储当前的深度,后面方便计算路径长度
	for (int i = lg[dep[x]] - 1;i>=0; i--)
	{
		if (f[x][i] != f[y][i])
		{
			x = f[x][i];
			y = f[y][i];
		}
	}
	//printf("x=%d\n", x);
	//最后的父节点是直接父节点
	ans += (tmp - dep[f[x][0]]) * 3;
	return ans;
}

int n;
int main()
{
	scanf("%d", &n);
	int x, y;
	for (int i = 1; i <= n - 1; i++)
	{
		scanf("%d%d", &x, &y);
		//printf("%d -- %d\n", x, y);
		add(x, y);
		//printf("%d\n", e[2].to);
		//add(y, x);
	}
	for (int i = 1; i <= n; i++)
		lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
	dfs(1, 0);//预处理树上倍增
	int width = 0;
	for (int i = 1; i <= maxDep; i++)
	{
		width = max(width, deps[i]);
	}
	scanf("%d%d", &x, &y);
	//计算路径
	int len = cal(x, y);//计算x->y的宽度
	printf("%d\n%d\n%d\n", maxDep, width, len);
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值