倍增小结 ST 与 LCA
倍增
倍增我是真滴不会
倍增法(英语:binary lifting),顾名思义就是翻倍。
能够使线性的处理转化为对数级的处理,大大地优化时间复杂度。
(ps:上次学倍增LCA,没学会,老老实实为了严格次小生成树滚回来重新学)
RMQ_QWQ
\(n~log(n)~\)的预处理与\(O~(1)\)的查询
- 设\(f_{i,j}\)表示区间\([i,i+2^j - 1]\)的最大值
- 一开始\(f_{i,0}=a_i\) (\(2^0 -1 = 0\) \(f_{i,0}\)的区间为\([i,i]\))
- 转移方程 ;
\[f_{i,~j} = max(f_{i,~j-1}, f_{i+2^{j-1},~~j-1})
\]
感性理解一下
对于每次询问\([l,~r]\)
- \(r = l + 2^x- 1\)
- \(x = log_2~ (r - l + 1)\)
- \(ans = max(f_{l,~l + 2^x-1},f_{r - 2^x + 1,~r})\)(此处的表达不是很准确,其实表达应该为)
\[f(l,r) = max(f_{l,~l + 2^x-1},f_{r - 2^x + 1,~r})
\]将这个\(f(l,r)\)理解为一个函数,可能就不会有太大的歧义了
Q:这里为什么不能直接用\(f_{i,~x}\) 呢?
A:因为我们这里的\(log\)是向下取整的,可能会出现有一块取不到的部分
Q:那有重复的部分怎么办呐??
A:重复部分对答案的贡献有影响吗?
Q:貌似莫得影响
A:
\(ans = max(f_{l,~x}, f_{r - 2^x + 1,~x})\)
完事
注意点¶
输入输出数据一般很多,建议开启输入输出优化。
每次用 std::log 重新计算 log 函数值并不值得,建议进行如下的预处理:
\]
\]
第二个式子是这样推导出来的
~~~~~~~~~~~~~~~= log_2 + log_2\frac{x}{2}\\
~~~~~~~~~=1+log_2\frac{x}{2}
\]
code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#define ll long long
using namespace std;
const int logn = 22;
const int N = 2000001;
int read() {
int s = 0, f = 0;
char ch = getchar();
while (!isdigit(ch))
f |= (ch == '-'), ch = getchar();
while (isdigit(ch))
s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
int Log[N];
int n ,m;
void pre() {
Log[1] = 0; Log[2] = 1;
for (int i = 3; i <= n; ++i)
Log[i] = Log[i / 2] + 1;
}
int f[N][25];
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++i) f[i][0] = read();
pre();
for (int j = 1; j <= logn; ++j)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
for (int i = 1; i <= m; i++) {
int l = read(), r = read();
int log_x = Log[r - l + 1];
printf("%d\n", max(f[l][log_x], f[r - (1 << log_x) + 1][log_x]));
}
system("pause");
return 0;
}
倍增求LCA
- 核心的思想就是每次找祖先的时候多跳几个以保证时间复杂度的优秀
先说朴素的算法
可以每次找深度比较大的那个点,让它向上跳。显然在树上,这两个点最后一定会相遇,相遇的位置就是想要求的 LCA。 或者先向上调整深度较大的点,令他们深度相同,然后再共同向上跳转,最后也一定会相遇.(摘自OI Wiki)
精简版本:
就是从深度深的向上跳,到达同一深度后一起向上跳(学过树剖的都知道吧,艹估计没人跟我一样先学的树剖,然后回来学倍增)
倍增就很优秀了
- 本质是朴素算法的改进算法。通过预处理\(f\)数组,可以使指针快速的移动\(f_{x,i}\) 表示点\(x\)的第\(2^i\)个祖先。这个过程需要通过\(dfs\) 预处理出来。
dfs
void dfs(int x, int fa) {
dep[x] = dep[fa] + 1;
f[x][0] = fa;
for (int i = 1; (1 << i) <= dep[x]; i++)
f[x][i] = f[f[x][i - 1]][i - 1];
for (int i = head[x]; i; i = e[i].next) {
int to = e[i].to;
if (to != fa)
dfs(to, x);
}
}
这种是较为朴素倍增的求法,没有用\(log\)优化
加入log优化的(但好像没太大的常数优化)
Lo数组还是原来的求法
void dfs(int x, int fa) {
f[x][0] = fa;
dep[x] = dep[fa] + 1;
for (int i = 1; i <=Log[dep[x]]; ++i)
f[x][i] =f[f[x][i - 1]][i - 1];
for (int i = head[x]; i; i = e[i].net)
if (e[i].to != fa)
dfs(e[i].to, x);
}
有一说一,我不大喜欢这种优化的方法我最喜欢的还是树剖
- 裸的倍增LCA,这种写法确实好理解
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 5e5 + 10;
struct tree {
int from, to, next;
} e[N << 1];
int nume, head[N];
void add_edge(int from, int to) {
e[++nume].from = from;
e[nume].to = to;
e[nume].next = head[from];
head[from] = nume;
}
int dep[N], f[N][21];
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
f[u][0] = fa;
for (int i = 1; (1 << i) <= dep[u]; i++)
f[u][i] = f[f[u][i - 1]][i - 1];
for (int i = head[u]; i; i = e[i].next) {
int to = e[i].to;
if (to != fa)
dfs(to, u);
}
}
int lca(int x, int y) {
if (dep[x] > dep[y])
swap(x, y);
for (int i = 20; i >= 0; i--) {
if (dep[x] <= dep[y] - (1 << i))
y = f[y][i];
}
if (x == y)
return x;
for (int i = 20; i >= 0; i--) {
if (f[x][i] == f[y][i])
continue;
x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int main() {
int n, m, s;
scanf("%d%d%d", &n, &m, &s);
for (int i = 1, x, y; i < n; i++) {
scanf("%d%d", &x, &y);
add_edge(x, y);
add_edge(y, x);
}
dfs(s, 0);
for (int i = 1, x, y; i <= m; i++) {
scanf("%d%d", &x, &y);
printf("%d\n", lca(x, y));
}
}
- 用\(Log\)的求法
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#define ll long long
using namespace std;
const int N = 5e5 + 10;
int read() {
int s = 0, f = 0;
char ch = getchar();
while (!isdigit(ch))
f |= (ch == '-'), ch = getchar();
while (isdigit(ch))
s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
struct Edge {
int from, to, net;
} e[N << 1];
int head[N], nume;
void add_edge(int from, int to) {
e[++nume].from = from, e[nume].to = to, e[nume].net = head[from],
head[from] = nume;
}
int f[N][25], Log[N], dep[N];
void dfs(int x, int fa) {
f[x][0] = fa, dep[x] = dep[fa] + 1;
for (int i = 1; i <= Log[dep[x]]; i++)
f[x][i] = f[f[x][i - 1]][i - 1];
for (int i = head[x]; i; i = e[i].net) {
int to = e[i].to;
if (to == fa)
continue;
dfs(to, x);
}
}
int lca(int x, int y) {
if (dep[x] < dep[y])
swap(x, y);
while (dep[x] > dep[y])
x = f[x][Log[dep[x] - dep[y]]];
if (x == y)
return x;
for (int i = Log[dep[x]] ; i >= 0; i--) {
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int main() {
int n = read(), m = read(), s = read();
for (int i = 1, u, v; i < n; i++) {
u = read(), v = read();
add_edge(u, v), add_edge(v, u);
}
Log[1] = 0;
for (int i = 2; i <= n; i++)
Log[i] = Log[i / 2] + 1;
dfs(s, 0);
for (int i = 1; i <= m; i++) {
int x = read(), y = read();
printf("%d\n", lca(x, y));
}
system("pause");
return 0;
}
嗷嗷嗷,有没有人喜欢树剖呢
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 5e5 + 10;
int head[N], nume;
struct node {
int from, to, next;
} e[N << 1];
void add_edge(int from, int to) {
e[++nume].from = from;
e[nume].to = to;
e[nume].next = head[from];
head[from] = nume;
}
int fath[N], siz[N], dep[N], son[N];
void dfs(int x, int fa) {
siz[x] = 1;
fath[x] = fa, dep[x] = dep[fa] + 1;
for (int i = head[x]; i; i = e[i].next) {
int to = e[i].to;
if (to == fa)
continue;
dfs(to, x), siz[x] += siz[to];
if (siz[son[x]] < siz[to])
son[x] = to;
}
}
int dfn[N], pre[N], top[N], cnt;
void dfs2(int x, int tp) {
dfn[x] = ++cnt, pre[cnt] = x, top[x] = tp;
if (son[x])
dfs2(son[x], tp);
for (int i = head[x]; i; i = e[i].next) {
int to = e[i].to;
if (to == fath[x] || to == son[x])
continue;
dfs2(to, to);
}
}
int lca(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]])
y = fath[top[y]];
else
x = fath[top[x]];
}
if (dep[x] < dep[y])
swap(x, y);
return y;
}
int n, m, s;
int main() {
scanf("%d%d%d", &n, &m, &s);
int u, v;
for (int i = 1; i < n; i++) {
scanf("%d%d", &u, &v);
add_edge(u, v), add_edge(v, u);
}
dfs(s, 0), dfs2(s, s);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", lca(x, y));
}
return 0;
}
倍增小结 ST 与 LCA的更多相关文章
- CF1039E Summer Oenothera Exhibition 贪心、根号分治、倍增、ST表
传送门 感谢这一篇博客的指导(Orzwxh) $PS$:默认数组下标为$1$到$N$ 首先很明显的贪心:每一次都选择尽可能长的区间 不妨设$d_i$表示在取当前$K$的情况下,左端点为$i$的所有满足 ...
- hdu6107 倍增法st表
发现lca的倍增解法和st表差不多..原理都是一样的 /* 整篇文章分成两部分,中间没有图片的部分,中间有图片的部分 分别用ST表求f1,f2表示以第i个单词开始,连续1<<j行能写多少单 ...
- 51Nod1766 树上的最远点对 ST表 LCA 线段树
原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1766.html 题目传送门 - 51Nod1766 题意 n个点被n-1条边连接成了一颗树,给出a~ ...
- hdu 2874 Connections between cities(st&rmq LCA)
Connections between cities Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (J ...
- HDU2874【倍增、ST】
题目链接[https://vjudge.net/problem/HDU-2874] 题意: 输入一个森林,总节点不超过N(N<10000),由C次询问(C<1000000),每次询问两个点 ...
- 倍增 - 强制在线的LCA
LCA 描述 给一棵有根树,以及一些询问,每次询问树上的 2 个节点 A.B,求它们的最近公共祖先. !强制在线! 输入 第一行一个整数 N. 接下来 N 个数,第 i 个数 F i 表示 i 的父亲 ...
- CF1190E Tokitsukaze and Explosion 二分、贪心、倍增、ST表
传送门 最小值最大考虑二分答案,不难发现当最小值\(mid\)确定之后,原点到所有直线的距离一定都是\(mid\)时才是最优的,也就是说这些直线一定都是\(x^2+y^2=mid^2\)的切线. 接下 ...
- st表 LCA
我当时知道ST表可以 \(O(1)\) 求 LCA 的时候是极为震惊的,可以在需要反复使用 LCA 的时候卡常使用. ST表!用于解决 RMQ问题 ST表 我可能写得不好,看专业的 怎么实现? 考虑把 ...
- ST和LCA和无根树连接
#include <stdio.h> #include <iostream> #include <string.h> #include <algorithm& ...
随机推荐
- 2.1JAVA文件基本结构
命名 包名 全为英文小写 项目包命名 域名反转.团队名.项目名 相关项目包命名 域名反转.团队名.父项目名.子项目名 类和接口名 所有单词首字母大写 抽象类 用"Abstract" ...
- android中VideoView播放sd卡上面的视频
(1)videoView组件只支持MP4和3gp格式的视屏播放,如果想播放其它视屏格式的文件,还得开发能够播放的视屏播放器 (2)videoView组件功能比较单一,如果想开发功能丰富的播放器,还得重 ...
- 每日一个linux命令6 -- mv
mv test.log test1.txt 文件改名 mv test1.log test3 文件移动 mv test1.log test2.log test3.log test4 将1,2,3.log ...
- 1001 害死人不偿命的(3n+1)猜想 (15分)
卡拉兹(Callatz)猜想: 对任何一个正整数 n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把 (3n+1) 砍掉一半.这样一直反复砍下去,最后一定在某一步得到 n=1.卡拉兹在 1950 ...
- RedHat6.1通过配置yum server安装软件包
1.获取镜像RHEL_6.1\ x86_64\ Disc\ 1 2.配置yum server #cd /etc/yum.repos.d #tar -cvf 20141114bak.tar *.repo ...
- Java 安全之Weblogic 2017-3248分析
Java 安全之Weblogic 2017-3248分析 0x00 前言 在开头先来谈谈前面的绕过方式,前面的绕过方式分别使用了streamMessageImpl 和MarshalledObject对 ...
- tf.argmax(vector,axis)函数的使用
1.返回值 vector为向量,返回行或列的最大值的索引号: vector为矩阵,返回值是向量,返回每行或每列的最大值的索引号. 2.参数 vector为向量或者矩阵 axis = 0 或1 0:返回 ...
- 【Java基础】Java 语言概述
Java 语言概述 主要应用场景 JavaEE.大数据.Android 开发方向. 基础知识概述 编程语言核心结构 变量.基本语法.分支.循环.数组.- Java 面向对象的核心逻辑 OOP.封装.继 ...
- 使用Jenkins+Blue Ocean 持构建自动化部署之安卓源码打包、测试、邮件通知
什么是BlueOcean? BlueOcean重新考虑了Jenkins的用户体验.BlueOcean由Jenkins Pipeline设计,但仍然兼容自由式工作,减少了团队成员的混乱,增加了清晰度. ...
- 在项目中应该使用Boolean还是使用boolean?
起因 在公司看代码时,看到了使用Boolean对象来完成业务逻辑判断的操作.和我的习惯不一致,于是引起了一些反思. boolean和Boolean的差别咱就不说了,我们仅探讨使用boolean与Boo ...