P3884 [JLOI2009]二叉树问题

news2024/12/24 3:19:11

题目

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

  • 深度: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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/124293.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法工程师需要学习的基础

文章目录应该早点系统地了解算法工程师需要学习的东西的&#xff0c;B站上的up主&#xff1a;梁唐讲的很好&#xff0c;大家可以去看一下&#xff0c;只截了一部分图做一个记录

MySQL5.7 多主一从(多源复制)同步配置

主从复制有如下一些优势&#xff1a; 分担负载&#xff1a;对业务进行读写分离&#xff0c;减轻主库I/O负载&#xff0c;将部分压力分担到从库上&#xff0c;缩短客户查询响应时间。 增加健壮性&#xff1a;在主库出现问题时&#xff0c;可通过多种方案将从库设置为主库&#…

100%全国产龙芯2K1000设计方案

国产工业处理器&#xff0c;龙芯2K1000主板&#xff0c;100%全国产化方案 可实现100%国产元器件方案&#xff0c;国产处理器 信迈2k1000开发板采用龙芯 2k1000处理器&#xff0c;处理器集成 2 个 GS264 处理器核&#xff0c;主频 1GHz&#xff0c;64 位 DDR3 控制器&#xff…

Vue与VueComponent的内置关系

上一节讲到了 Vue.extend 与 VueComponent 的区别&#xff0c;这一节讲一讲 Vue 与 VueComponent的内置关系。 原型与原型链 这里需要用到原型与原型链中的知识点&#xff0c;具体文章链接在这里。js中的原型与原型链 这里只需要理解一个点&#xff0c;那就是构造函数的protot…

【idea2022.3】安装教程2022-12

教程目录教程简介所需环境和版本软件下载执行过程安装激活开始使用前的配置结束语教程简介 换电脑了&#xff0c;又经历了一遍软件和环境的安装&#xff0c;已经安装好了jdk和对应版本的maven&#xff0c;所以接下来该安装idea等软件了 所需环境和版本 系统&#xff1a;win1…

软件测试人员30K的月薪,是个什么段位?

大家可以参照BAT等一线大厂的职级&#xff0c;一般是高级测试工程师和资深测试开发工程师的职位&#xff0c;下面是在字节跳动年薪40W的测试工程师 掌握的技能树主要包含哪个方面&#xff1f; 现在的行情&#xff0c;大家想要拿到30k这个薪资&#xff0c;不妨审视自己&#xf…

跨平台备份工具Duplicati

本文软件得到了网友 冷心 的推荐&#xff1b; 什么是 Duplicati &#xff1f; Duplicati 是一个免费的开源备份客户端&#xff0c;可将加密的、增量的、压缩的备份安全地存储在云存储服务和远程文件服务器上。支持 Amazon S3、IDrive e2、Backblaze (B2)、Box、Dropbox、FTP、G…

【Flask框架】——27 SQLAlchemy高级

1、排序 order_by方法排序&#xff1a;可以指定根据模型中某个属性进行排序&#xff0c;"模型名.属性名.desc()"代表的是降序排序。 # 根据年龄降序 lst session.query(Student).order_by(Student.age.desc()).all() # 根据年龄升序 lst session.query(Student).…

WinNTSetup V5.3.0 Bata5 单文件版

前言 WinNTSetup 是一款Windows系统硬盘安装器&#xff0c;支持从PE和本地安装系统&#xff0c;支持支持NT内核的系统。 WinNTSetup 包括XP、Win7、Win8、Win8.1、Win10等这些系统。直接从硬盘安装系统&#xff0c;不需要光盘。WinNTSetup 还附加一些系统优化功能&#xff0…

Android---简易的底部导航栏

目录 一、activity_main.xml布局 二、给ViewPager2 创建适配器 三、ViewPager2 数据源 四、MainActivity.java类 1、初始化数据源。 2、ViewPager2 页面改变监听 3、BottomNavigationView 的每个 item 点击的监听 这里简单演示实现效果&#xff0c;实现快速开发&#xff…

mmdetection从入门到精通(一)-汇总目录

&#xff11;.简介 MMDetection 是商汤出品的集成了目标检测&#xff0f;实例分割分割&#xff0f;全景分割几个方面顶级模型组合的,模块化的&#xff0c;基于 PyTorch 的目标检测开源工具箱。是深度学习工作者的必备工具&#xff0c;非常有必要深入掌握。 近期汇总一下从入…

今年最后一场官方活动

阅读本文大概需要 1.6 分钟。2022 年 12 月 26&#xff0c;新型冠状病毒肺炎正式更名为了新型冠状病毒感染&#xff0c;不再纳入《中华人民共和国国境卫生检查疫法》 规定的检疫传染病管理。文字层面上&#xff0c;这意味着我们口中的疫情&#xff0c;结束了。然而我却认为并没…

Django学习 Day9

1.F对象 一个F对象代表数据库中某条记录的字段的信息。 作用&#xff1a; 通常是对数据库中的字段的值在不获取的情况下进行操作 用于类属性&#xff08;字段&#xff09;之间的比较。 语法&#xff1a; From django.db.models import F F(‘列名’)例子: 所有Book数据表中的…

云游戏的2022:破局、新生、元宇宙

文|智能相对论 作者|青月 如果说2021年是「元宇宙元年」&#xff0c;那么2022年更像是元宇宙的「祛魅之年」&#xff0c;在这一年里&#xff0c;原本处在狂奔状态下的元宇宙正在褪去虚火。 在这样的大环境下&#xff0c;由于在实时性、兼容性、无限开创等关键特性的理念上的…

99. 激光炸弹——二维前缀和

地图上有 N 个目标&#xff0c;用整数 Xi,Yi 表示目标在地图上的位置&#xff0c;每个目标都有一个价值 Wi。 注意&#xff1a;不同目标可能在同一位置。 现在有一种新型的激光炸弹&#xff0c;可以摧毁一个包含 RR 个位置的正方形内的所有目标。 激光炸弹的投放是通过卫星定…

eclipse中安装ERMaster

eclipse中安装ERMaster 简介 参考网址&#xff1a; https://www.bilibili.com/video/BV1R4411a73T/?p22&spm_id_from333.880.my_history.page.click&vd_source42661b67a37800001020550eb4a4c45e 主要看这 3 集 ERMaster 在 jeesite 官网的介绍项目 参考网址&#x…

systemd(二)单元配置文件

概述 对于系统中的每一个单元&#xff08;unit&#xff09;都有一个配置文件&#xff0c;用于指示systemd如何启动或停止这个单元。 配置文件格式 [Unit]区块 [Unit]区块通常是配置文件的第一个区块&#xff0c;用来定义单元的元数据&#xff0c;以及配置与其他单元的关系。…

多线程模式下保证事物的一致性

目录前置InsertBatchSuccessServiceImpl.javaInsertBatchErrorServiceImpl.java效果图前置 在一些特殊的场景下, 我们需要一些特定的操作. 比如我有一个接口, 做如下操作, 需要保持事物的一致性, 即: 全部成功则提交, 一个异常则全部回滚: 1.insert订单、(耗时1秒) 2.insert订单…

4、MYSQL常用函数(字符串函数)

目录 1、concat函数&#xff1a; 2、insert(str,x,y,instr)函数&#xff1a; 3、lower(str)和upper(str)函数&#xff1a; 4、left(str,x)和right(str,x)函数&#xff1a; 5、lpad(str,n,pad) 和rpad(str,n,pad) 函数&#xff1a; 6、ltrim(str)和rtrim(str)函数&#xff…

【大厂高频真题100题】《除自身以外数组的乘积》 真题练习第19题 持续更新~

除自身以外数组的乘积 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请不要使用除法,且在 O(n) 时间复杂度内完成…