首页 > 其他分享 >关于 LCA 的简单记录

关于 LCA 的简单记录

时间:2023-08-06 23:25:51浏览次数:31  
标签:nxt head 记录 int 隔间 简单 lca include LCA

最近做了几道 LCA 的题目。所以总结一下。

首先我们来回顾一下倍增求 LCA 的流程吧。

首先是初始化:

  • 进行 bfs。
  • 处理出每层的深度。
  • 处理每个节点的 \(2^k\) 级父亲,方式为一个递推,即为由 \(2^{k-1}\) 级祖先的 \(2^{k - 1}\) 祖先推出 \(2^k\) 级祖先。

然后是每次的查询:

  • 把 y 的高度提到和 x 相同。运用向上提 \(2^k\) 级的方法,如果不超过 x 的话就继续提。
  • 对于 \(k\) 从高到低尝试,如果 x 和 y 的第 \(2^k\) 级祖先不同的话,那么把这二者同时向上提 \(2^k\) 级,根据二进制分解的知识,我们知道用这种方法能向上提任意的数。那么这会使得二者尽可能向上提,使得二者仅差一步就能相会。
  • 那么仅差一步的话,直接返回任意一个的父亲就好了。

简单例题:聚会

Y 岛风景美丽宜人,气候温和,物产丰富。Y 岛上有 \(N\) 个城市,有 \(N-1\) 条城市间的道路连接着它们。每一条道路都连接某两个城市。幸运的是,小可可通过这些道路可以走遍 \(Y\) 岛的所有城市。神奇的是,乘车经过每条道路所需要的费用都是一样的。小可可,小卡卡和小 YY 经常想聚会,每次聚会,他们都会选择一个城市,使得 3 个人到达这个城市的总费用最小。 由于他们计划中还会有很多次聚会,每次都选择一个地点是很烦人的事情,所以他们决定把这件事情交给你来完成。他们会提供给你地图以及若干次聚会前他们所处的位置,希望你为他们的每一次聚会选择一个合适的地点。

题意概括:3 个点在树上,求使这 3 个点联通的树上最短路径。

解法

这道题简单到飞,我们发现可以仅仅先对 x 和 y 求 lca,然后便可以对于 z 进行分类讨论。

z 的情况分为两大类:

  • 是 x 和 y 的 lca 的祖先或者和 x 和 y 的 lca 没有亲辈和子辈关系
  • x、 y、 z 的公共 lca 是 x 和 y 的 lca

其中第一种情况的解决方案是,集合地点为 x 和 y 的 lca,然后再计算 z 到 lca 的距离,求总和。

而第二种也分两大类,路径为一条线或者带有一个分叉,即为:

  • z 为 x 或者 y 的祖先
  • z 既非 x 的祖先也非 y 的祖先

前者的解决方案是,集合地点为 z 点,距离为 x 和 y 的距离。

而带有分叉的情况,即后者,若和 x 的 lca 的深度深于和 y 的,那么集合地点选在和 x 的 lca 处,距离为 x 和 y 的距离加 z 到和 x 的 lca 的距离。反之亦然。

那么代码就容易写出了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>

using namespace std;

const int maxn = 5e5 + 5;

struct E {
	int nxt, to;
} e[maxn << 1];

int n, p, cnt, head[maxn], t;
int f[maxn][23], d[maxn];
queue<int> q;

void add(int a, int b) {
	e[++ cnt].to = b;
	e[cnt].nxt = head[a];
	head[a] = cnt;
}

void bfs() {
	q.push(1);
	d[1] = 1;
	while(q.size()) {
		int x = q.front();
		q.pop();
		for(int i = head[x]; i; i = e[i].nxt) {
			int y = e[i].to;
			if(d[y]) continue;
			d[y] = d[x] + 1;
			f[y][0] = x;
			for(int j = 1; j <= t; j ++) 
				f[y][j] = f[f[y][j - 1]][j - 1];
			q.push(y);
		}
	}
}

int lca(int x, int y) {
	if(d[x] > d[y]) swap(x, y);
	for(int i = t; i >= 0; i --)
		if(d[f[y][i]] >= d[x])
			y = f[y][i];
	if(x == y) return x;
	for(int i = t; i >= 0; i --)
		if(f[x][i] != f[y][i])
			x = f[x][i], y = f[y][i];
	return f[x][0];
}

int main() {
	scanf("%d%d", &n, &p);
	t = (int) (log(n) / log(2)) + 1;
	for(int i = 1, u, v; i < n; i ++) {
		scanf("%d%d", &u, &v);
		add(u, v), add(v, u);
	}
	bfs();
	for(int i = 1, x, y, z; i <= p; i ++) {
		scanf("%d%d%d", &x, &y, &z);
		int lcaxy = lca(x, y);
		int lcaxyz = lca(lcaxy, z);
		if(lcaxyz != lcaxy && lcaxyz != z) {
			printf("%d %d\n", lcaxy, d[x] + d[y] - d[lcaxy] + d[z] - 2 * d[lcaxyz]);
		} else if(lcaxyz == z) {
			printf("%d %d\n", lcaxy, d[x] + d[y] - d[lcaxy] + d[z] - 2 * d[lcaxyz]);
		} else if (lcaxyz == lcaxy) {
			int lcaxz = lca(x, z), lcayz = lca(y, z);
			if(lcaxz == z || lcayz == z) {
				printf("%d %d\n", z, d[x] + d[y] - 2 * d[lcaxy]);
			} else {
				printf("%d %d\n", d[lcaxz] > d[lcayz] ? lcaxz : lcayz, 
								  d[lcaxz] > d[lcayz] ? d[x] + d[y] - 2 * d[lcaxy] + d[z] - d[lcaxz] : d[x] + d[y] - 2 * d[lcaxy] + d[z] - d[lcayz]);
			}
		}
	}
}

简单例题2:[USACO15DEC] Max Flow P

FJ 给他的牛棚的 \(N\) 个隔间之间安装了 \(N-1\) 根管道,隔间编号从 \(1\) 到 \(N\)。所有隔间都被管道连通了。

FJ 有 \(K\) 条运输牛奶的路线,第 \(i\) 条路线从隔间 \(s_i\) 运输到隔间 \(t_i\)。一条运输路线会给它的两个端点处的隔间以及中间途径的所有隔间带来一个单位的运输压力,你需要计算压力最大的隔间的压力是多少。

解法

这题是一个典型的树上差分问题。具体来讲是先进行倍增 lca 的处理。然后对于每个点的值进行记录。

采用的是经典的差分思想:通过计算差分数组的前缀和即可推断出原数组。

对于每条线路 \((s_i, t_i)\),在两端各加上 1 点的权值,在两点的 lca 处减少 1 点权值,在两点的 lca 的父亲节点处减少 1 点的权值。这样就可以在这条路径上增加 1 的权值了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>

using namespace std;

const int maxn = 5e4 + 5;
int n, k;

struct E {
	int nxt, to;
} e[maxn << 1];

int cnt, head[maxn], f[maxn][23], d[maxn], t, p[maxn];
int ans;
queue<int>	q;

void add(int u, int v) {
	e[++ cnt].to = v;
	e[cnt].nxt = head[u];
	head[u] = cnt;
}

void bfs() {
	q.push(1);
	d[1] = 1;
	while(q.size()) {
		int x = q.front();
		q.pop();
		for(int i = head[x]; i; i = e[i].nxt) {
			int y = e[i].to;
			if(d[y]) continue;
			d[y] = d[x] + 1;
			f[y][0] = x;
			for(int j = 1; j <= t; j ++) 
				f[y][j] = f[f[y][j - 1]][j - 1];
			q.push(y);
		}
	}
}

int lca(int x, int y) {
	if(d[x] > d[y]) swap(x, y);
	for(int i = t; i >= 0; i --)
		if(d[f[y][i]] >= d[x])
			y = f[y][i];
	if(x == y) return x;
	for(int i = t; i >= 0; i --)
		if(f[x][i] != f[y][i])
			x = f[x][i], y = f[y][i];
	return f[x][0];
}

void calc(int x, int fa) {
	for(int i = head[x]; i; i = e[i].nxt) {
		int y = e[i].to;
		if(y == fa) continue;
		calc(y, x);
		p[x] += p[y];
	}
	ans = max(ans, p[x]);
}

int main() {
	cin >> n >> k;
	t = (int) (log(n) / log(2)) + 1;
	for(int i = 1, x, y; i < n; i ++) {
		cin >> x >> y;
		add(x, y), add(y, x);
	}
	bfs();
	for(int i = 1, a, b; i <= k; i ++) {
		cin >> a >> b;
		p[a] ++, p[b] ++, p[lca(a, b)] --, p[f[lca(a, b)][0]] --;
	}
	calc(1, 0);
	cout << ans << endl;
}

标签:nxt,head,记录,int,隔间,简单,lca,include,LCA
From: https://www.cnblogs.com/Inversentropir-36/p/17586107.html

相关文章

  • C关于一维数组以及二维数组的创建和简单利用(下)
    #include<stdio.h>intmain(){inta[3][4]={{1,2,3,4},{1,2,3,4},{1,2,3,4}};intb=0;for(b=0;b<3;b+=1){intc=0;for(c=0;c<4;c+=1){printf("%p||",&a[b][c]);......
  • CUDA简单介绍
    并行计算并行计算(parallelcomputing)是一种计算形式,它将大的问题分解为许多可以并行的小问题。并行计算分为:任务并行(taskparallel)和数据并行(dataparallel)任务并行指多个任务同时执行数据并行指多个数据可以同时处理,每个数据由独立的线程处理数据并行分块方法有:块分(......
  • SUの挂分记录(物理)
    Cnblogs阅读2023.08.04不注意滑动变阻器最大通过电流,导致最大电流算大了2023.08.06没注意干路电流超过电流表量程,导致最大电功率中电流判断错误2023.08.06原子核中蕴藏的是核能,而不是化学能或电能Updating.........
  • 模拟赛记录
    ZROI暑假集训T1给定序列\(a_i\)和\(d\),找出最长的子区间使得区间内元素排序后相差不超过\(d\)。人类智慧题两个限制:编号连续和差值的限制,两个分开维护都好做,但是结合起来比较麻烦,考虑每次把区间按照其中一个限制处理得到若干小区间,再把这些小区间按另一个限制处理,直到得......
  • 通过 ChatGPT 赚钱:2023 年成功的简单策略
    欢迎来到技术与创收相结合的可能性世界!如果您曾经想过如何通过ChatGPT赚钱,那么您将进入一段激动人心的旅程。在本指南中,我们将探讨简单有效的策略,使您能够利用ChatGPT的强大功能来创造收入来源。无论您是内容创建者、企业主还是希望分享您的专业知识的人,ChatGPT都提供了一种......
  • 【JavaScript06】简单运算符与数据类型转换
    简单运算符1、&&,||有短路的含义,如果前面的表达式可以得出最终结果了.那么后面的表达式就不计算了vara=10;varb=20;varc=30;console.log(a>b&&b<c);console.log(b<c||a>b);2、==和=====只是判断值是否一致​===会判断数据类型和数据是......
  • 记录一下 搭建springboot,springCloud,springCloudAlibaba,nacos
    1,首先创建一个空项目里面有两个服务一个提供者一个调用者 2,父工程的使用依赖 以及springBoot的父依赖//springboot父工程<parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId>......
  • 记录小知识 数据库设置自动填充更新创建字段时间
    1,在数据库中设置该字段类型为timestamp  2,设置默认值为 CURRENT_TIMESTAMP3,更新字段需要点击勾选根据当前时间戳更新 而创建时间是不需要勾选的因为创建只需要一次 ......
  • 记录小知识 springboot,maven创建的多模块 子模块无法使用父类版本
    使用依赖时发现依赖有问题,回来检查发现没有加springboot父工程检查父模块是否加入父标签:只需要在父模块中添加一次就可以了<parent><groupId>org.springframework.boot</groupId><cartifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELE......
  • 11python日志类的简单应用
    代码如下:importlogging#日志类简单应用,方便规范格式化输出日志deft():foriinrange(10):logging.info("print%s",i)logging.error('发送错误')if__name__=='__main__':logging.basicConfig(level=logging.INFO,format='%(......