Lowest Common Ancestor

模板

1. Tarjan

一个讲的很好的视频:D10 Tarjan算法 P3379【模板】最近公共祖先(LCA)_哔哩哔哩_bilibili,董晓算法出品。

Tarjan总体来说可以概括为:

  1. 记录访达:记录某个节点是否已经访问过,防环
  2. 向下深搜:深搜子节点
  3. 回溯指父:低层回溯时将子节点归于当前父节点所在等价类中
  4. 离时查询:本层向上回溯时查询与当前节点所有相关的LCA,记录答案
package Tarjan.LCA;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class TarjanLCA {private List<Integer>[] e;private List<int[]>[] query;private int[] fa;private boolean[] vis;private int[] ans;/*** 求LCA* @param edge 边集* @param queries 查询* @param n 总共几个节点* @return 查询对应的LCA集合*/public int[] Tarjan(int[][] edge,int[][] queries,int n,int root){e = new ArrayList[n];Arrays.setAll(e,e->new ArrayList<>());query = new ArrayList[n];Arrays.setAll(query,e->new ArrayList<>());fa = new int[n];for (int i = 0; i < fa.length; i++) {fa[i] = i;}vis = new boolean[n];Arrays.fill(vis,false);ans = new int[queries.length];// 邻接表建边for (int[] es : edge) {e[es[0]].add(es[1]);e[es[1]].add(es[0]);}// tarjan 查询数组for (int i = 0; i < queries.length; i++) {int[] qs = queries[i];query[qs[0]].add(new int[]{qs[1],i});query[qs[1]].add(new int[]{qs[0],i});}dfs(root);return ans;}private void dfs(int node){vis[node] = true;for (Integer child : e[node]) {if(!vis[child]){dfs(child);fa[child] = node;}}// 向上一层返回时记录LCAfor (int[] q : query[node]) {if(vis[q[0]]){ans[q[1]] = find(q[0]);}}}private int find(int x){if(fa[x]!=x){fa[x] = find(fa[fa[x]]);}return fa[x];}}

这里有几个要注意的地方:

  1. 查询数组记得要对称设置,比如查3,4的lca,3要放1个,4也要放1个。因为到底哪个先深搜到是不确定的。比如就放了3的,那如果先深搜到3,发现此时4压根就没指父过(压根没访问到),这个查询对应的答案就没法记录了。所以都放一个绝对可以防止深搜顺序的不确定性。
  2. 并查集的路径压缩并不会影响查询结果。因为是回溯时查询,所以绝对是从低层起的,随着逐渐往根处的回溯,并查集中等价类会逐渐向根扩张,并以根为祖宗节点。

这里放一个例子,可以对照代码手玩一下:

     	    0/   \4     3/|\     \1 5 6     8/ \2   7

测试用例可自选。

1. LC 2846 边权重均等查询

树的定义是连通无回路的图,所以会有以下性质:

  1. 任意两个节点间有且仅有一条通路
  2. 任意节点至多有一个父节点

所以要查询任意两个节点之间的最小操作次数,可以唯一地确定答案。因为这两个节点之间存在且仅只存在一条通路。

这个贪心其实很显然,就是对于一条链,让其他权重向频率最大的那个靠近即可。比如这条链上的权重为:

[ 1 , 1 , 2 , 2 , 2 , 3 ]

很显然答案是把1和3全部变成2,操作次数是3。

那么怎么计算这条链上的操作次数呢?这里定义i→j为从节点i到节点j上的链上各权重出现频次。计算公式为:

op(i->j) = ∑(op(0->i) + op(0->j) - 2*op(0->lca(i,j)))
其中lca表示最近公共祖先

举个例子:

            0/   \4     3/|\     \1 5 6     8/ \2   7
  1. 假设我们要看6→7这条链,那么可以计算 0→6 + 0→7 - 0→4 - 0→4的各权重出现频次。因为0→4多算了两次那么这个公式是否具有普适性呢?
  2. 假设现在要看4→3这条链,可以计算0→4+0→3 - 2* 0→0,也是适用的。

所以思路就是,我们先通过深搜,求出来每个节点到根节点(一颗无向树,谁都可以作为根节点,不妨设为0)0的链上各权重出现频次。然后利用tarjan求出来每组查询的公共祖先,带入上述公式计算即可。

深搜求频次的思路是:由于本层递归比上一层就多了一个上一层节点到本层节点的权重,因此我们可以复制上一层节点(本层节点的父节点)的各权重频次,再在当前权重上增1即可。

而利用性质2,可以简单的记录fa节点判环。但tarjan是不能这样做的,因为需要明确离时查询时另一个节点是否已经访问,并不只是简单的判环功能。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;class Solution {List<int[]>[] e;List<int[]>[] qs;int[][] cnt;int[] lca;int[] fa;boolean[] vis;int[] ans;public int[] minOperationsQueries(int n, int[][] edges, int[][] queries) {e = new ArrayList[n];qs = new ArrayList[n];Arrays.setAll(e,e->new ArrayList<>());Arrays.setAll(qs,e->new ArrayList<>());int u,v,w;//邻接表for (int[] edge : edges) {u = edge[0];v = edge[1];w = edge[2];e[u].add(new int[]{v,w});e[v].add(new int[]{u,w});}// tarjan 查询for (int i = 0; i < queries.length; i++) {int[] q = queries[i];qs[q[0]].add(new int[]{q[1],i});qs[q[1]].add(new int[]{q[0],i});}cnt = new int[n][26];cnt_dfs(0,-1,0);lca = new int[queries.length];fa = new int[n];for (int i = 0; i < fa.length; i++) {fa[i] = i;}vis = new boolean[n];Arrays.fill(vis,false);tarjan(0);ans = new int[queries.length];calAns(queries);return ans;}private void cnt_dfs(int node,int father,int weight){if(father!=-1){cnt[node] = Arrays.copyOf(cnt[father],26);cnt[node][weight-1]++;}for (int[] child : e[node]) {if(child[0]!=father){cnt_dfs(child[0],node,child[1]);}}}private void tarjan(int node){vis[node] = true;for (int[] child : e[node]) {if(!vis[child[0]]){tarjan(child[0]);// tarjan 回溯指父fa[child[0]] = node;}}// 离时查询for (int[] q : qs[node]) {if(vis[q[0]]){lca[q[1]] = find(q[0]);}}}private int find(int x){if(fa[x]!=x){fa[x] = find(fa[fa[x]]);}return fa[x];}private void calAns(int[][] queries){int sum,max;for (int index = 0; index < queries.length; index++) {sum = 0;max = Integer.MIN_VALUE;int u = queries[index][0];int v = queries[index][1];for(int i=0;i<26;i++){int freq = cnt[u][i] + cnt[v][i] - 2*cnt[lca[index]][i];sum += freq;max = Math.max(freq,max);}ans[index] = sum-max;}}}

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

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

相关文章

3. MATLAB中Plot绘制放大特定的区域

在MATLAB中&#xff0c;我们经常需要绘制图形并进行一些自定义的操作。在本示例中&#xff0c;我们将演示如何在MATLAB中绘制一个图形&#xff0c;并通过放大某个特定的区域来突出显示。 ## 原始图形 首先&#xff0c;我们绘制了一个包含正弦和余弦函数的图形。 % MATLAB 代…

RabbitMQ 笔记二

1.Spring 整合RabbitMQ 生产者消费者 创建生产者工程添加依赖配置整合编写代码发送消息 创建消费者工程添加依赖配置整合编写消息监听器 2.创建工程RabbitMQ Producers spring-rabbitmq-producers <?xml version"1.0" encoding"UTF-8"?> <pr…

【计算机图形学】实验五 一个简单的交互式绘图系统(实验报告分析+截图+源码)

可以先看一看这篇呀~【计算机图形学】专栏前言-CSDN博客https://blog.csdn.net/m0_55931547/article/details/135863062 目录 一、实验目的 二、实验内容

77 C++对象模型探索。虚函数- 从静态联编,动态联编出发,分析 虚函数调用问题探究

什么叫做单纯的类&#xff1a; 比较简单的类&#xff0c;尤其不包括 虚函数 和虚基类。 什么叫不单纯的类&#xff1a; 从上一章的学习我们知道&#xff0c;在某些情况下&#xff0c;编译器会往类内部增加一些我们看不见但是真实存在的成员变量&#xff0c;例如vptr&#xff…

unitary MUSIC 算法

unitary MUSIC 算法 论文 A Unitary Transformation Method for Angle-of-Arrival Estimation 中提出了 unitary MUSIC 的算法&#xff0c;直译就是酉 MUSIC 算法&#xff0c;即酉变换 MUSIC 算法。该算法的目的是简化计算复杂度&#xff0c;将传统 MUSIC 算法中的复数 SVD 和复…

【shell-10】shell实现的各种kafka脚本

kafka-shell工具 背景日志 log一.启动kafka->(start-kafka)二.停止kafka->(stop-kafka)三.创建topic->(create-topic)四.删除topic->(delete-topic)五.获取topic列表->(list-topic)六. 将文件数据 录入到kafka->(file-to-kafka)七.将kafka数据 下载到文件-&g…

Linux内核中USB设备驱动实现

USB 设备驱动&#xff1a; 一、USB 描述符&#xff1a;&#xff08;存在于USB 的E2PROM里面&#xff09; 1、 设备描述符&#xff1a;struct usb_device_descriptor 2、 配置描述符&#xff1a;struct usb_config_descriptor 3、 接口描述符&#xff1a;struct usb_interfa…

linux深度学习开发基础命令——极简版

linux深度学习开发基础命令——极简版 本博客只是阐述常用的部分shell命令&#xff0c;更为全面的内容请参考其他博客 1. 创建python虚拟环境 默认使用conda创建 conda create -yourenv_name pyhton3.x 查看全部虚拟环境 conda env list 激活虚拟环境 conda activate env_name …

GO——GPM

参考&#xff1a;https://juejin.cn/post/6844904130398404616 并发模型 参考&#xff1a;https://zhuanlan.zhihu.com/p/137339439 多进程 要点 主进程监听每进来一个请求&#xff0c;fork子进程处理 缺点 进程占用高&#xff0c;服务器负载高进程间通信困难 参考&#xff…

2024 CKA 题库 | 15、备份还原 etcd

不等更新题库 文章目录 15、备份还原 etcd题目:考点&#xff1a;参考链接:解答:备份快照恢复快照 检查 15、备份还原 etcd 题目: 设置配置环境 此项目无需更改配置环境。但是&#xff0c;在执行此项目之前&#xff0c;请确保您已返回初始节点。 [candidatemaster01] $ exit #…

【Deeplabv3+】Ubutu18.04中使用pytorch复现Deeplabv3+第三步)-----CityscapesScripts生成自己的标签

本文是在前面两篇文章的基础上&#xff0c;讲解如何更改训练数据集颜色&#xff0c;需要与前面两篇文章连起来看。 本文用于修改cityscapes数据集的标签颜色与Semankitti数据集的标签一致&#xff0c;对修改后的数据集进行训练。需要下载两个开发工具包和一个数据集&#xff0…

Git标签推送

标签默认属于本地分支&#xff0c;推送分支的时候并不会上传。需要自己手动推送 通过命令 git push origin <tagname>推送指定的标签 通过命令git push origin --tags批量推送所有的标签 在VS里打开git命令行窗口的方法&#xff1a;Git更改-操作-打开命令行提示符 对于…

1.19信息学,信息熵(wordle)

所谓均方误差实际上就是方差 分析&#xff1a;对单词进行编码后&#xff0c;采用聚类方法&#xff0c;可以将单词难度分为三类或者更多&#xff0c;如困难、一般、简单。然后对每一类的单词可视化分析&#xff0c;并描述数据得出结论。 聚类算法较多&#xff0c;在论文中可以…

Docker镜像

创建镜像有三种方法&#xff0c;分别为基于已有镜像创建、基于本地模板创建以及基于Dockerfile创建。 基于现有镜像创建 首先启动一个镜像&#xff0c;在容器里做修改 然后将修改后的容器提交为新的镜像&#xff0c;需要使用该容器的 ID 号创建新镜像 常用选项&#xff1a; -…

【Unity】【游戏开发】Pico打包后项目出现运行时错误如何Debug

【背景】 开发过程中的报错可以通过控制台查看&#xff0c;但是PICO项目这类依赖特定设备环境的应用往往存在打包后在设备端发生运行时错误。这时如何能查看到Debug信息呢&#xff1f; 【分析】 Pico也是安卓系统&#xff0c;所以这个问题就可以泛化为Unity有哪些在安卓端运…

Linux系统中编写bash脚本进行mysql的数据同步

一、为何要用脚本做数据同步 &#xff08;一&#xff09;、问题 我们的视频监控平台云服务器&#xff0c;需要向上级的服务器定期同步一些数据表的数据&#xff0c;前期做了个程序&#xff0c;可以实现同步。但是&#xff0c;现在数据库的结构改了&#xff0c;结果又需要该程序…

C++实现推箱子游戏

推箱子游戏 运行之后的效果如视频所示&#xff0c;在完成游戏后播放音乐 准备工作&#xff1a;建立一个新的文件夹&#xff0c;并在文件夹中任意增加一张背景图片&#xff0c;以及各个部件的照片文件 因为这里用到了贴图技术&#xff0c;要使用graphic.h这个函数&#xff0c…

用于 C/C++ Debug 的宏函数

Debug.h 用于打印 Debug 信息的小工具&#xff1a; #pragma once#include <stdio.h>#define DEBUG 1#define pn puts("") #define where printf("%s(%d)-<%s>.\n\n", __FILE__, __LINE__, __FUNCTION__)#if (DEBUG 1) #define print_error(…

海外云手机三大优势

在全球化潮流下&#xff0c;企业因业务需求对海外手机卡等设备的需求不断攀升&#xff0c;推动了海外云手机业务的蓬勃发展。相较于自行置备手机设备&#xff0c;海外云手机不仅能够降低成本&#xff0c;还具备诸多优势&#xff0c;让我们深入探讨其中的三大黄金优势。 经济实惠…

【Linux】进程概述

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…