倍增

倍增我是真滴不会

倍增法(英语:binary lifting),顾名思义就是翻倍。

能够使线性的处理转化为对数级的处理,大大地优化时间复杂度。

(ps:上次学倍增LCA,没学会,老老实实为了严格次小生成树滚回来重新学)

RMQ_QWQ

ST表

\(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 1 = 0
\]
\[Log_2 x = log_2 \frac{x}{2} + 1
\]

第二个式子是这样推导出来的

\[log_2~x = log_2~2\times\frac{x}{2}\\
~~~~~~~~~~~~~~~= 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的更多相关文章

  1. CF1039E Summer Oenothera Exhibition 贪心、根号分治、倍增、ST表

    传送门 感谢这一篇博客的指导(Orzwxh) $PS$:默认数组下标为$1$到$N$ 首先很明显的贪心:每一次都选择尽可能长的区间 不妨设$d_i$表示在取当前$K$的情况下,左端点为$i$的所有满足 ...

  2. hdu6107 倍增法st表

    发现lca的倍增解法和st表差不多..原理都是一样的 /* 整篇文章分成两部分,中间没有图片的部分,中间有图片的部分 分别用ST表求f1,f2表示以第i个单词开始,连续1<<j行能写多少单 ...

  3. 51Nod1766 树上的最远点对 ST表 LCA 线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1766.html 题目传送门 - 51Nod1766 题意 n个点被n-1条边连接成了一颗树,给出a~ ...

  4. hdu 2874 Connections between cities(st&rmq LCA)

    Connections between cities Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (J ...

  5. HDU2874【倍增、ST】

    题目链接[https://vjudge.net/problem/HDU-2874] 题意: 输入一个森林,总节点不超过N(N<10000),由C次询问(C<1000000),每次询问两个点 ...

  6. 倍增 - 强制在线的LCA

    LCA 描述 给一棵有根树,以及一些询问,每次询问树上的 2 个节点 A.B,求它们的最近公共祖先. !强制在线! 输入 第一行一个整数 N. 接下来 N 个数,第 i 个数 F i 表示 i 的父亲 ...

  7. CF1190E Tokitsukaze and Explosion 二分、贪心、倍增、ST表

    传送门 最小值最大考虑二分答案,不难发现当最小值\(mid\)确定之后,原点到所有直线的距离一定都是\(mid\)时才是最优的,也就是说这些直线一定都是\(x^2+y^2=mid^2\)的切线. 接下 ...

  8. st表 LCA

    我当时知道ST表可以 \(O(1)\) 求 LCA 的时候是极为震惊的,可以在需要反复使用 LCA 的时候卡常使用. ST表!用于解决 RMQ问题 ST表 我可能写得不好,看专业的 怎么实现? 考虑把 ...

  9. ST和LCA和无根树连接

    #include <stdio.h> #include <iostream> #include <string.h> #include <algorithm& ...

随机推荐

  1. 关于eclipse反编译插件不起作用问题的解决

    1.首先我的eclipse版本是 Version: Photon Release (4.8.0),小伙伴们可以通过 help>>About eclipse IDE 来查看自己的eclips ...

  2. Spring Cloud Config原码篇(十)

    上篇中说到通过@Value注解获取配置中心的内容进行注入,要想了解这个就要知道spring Environment原理,关于这原理我看了下网上分析的文章:https://blog.csdn.net/t ...

  3. Centos7无网络下安装mysql5.7——mysql-rpm安装

    本教程指将mysql安装到系统默认目录下,如想自定义修改目录,请在rpm安装时自行修改: rpm -ivh --prefix= /opt xxx.rpm #将xxx.rpm安装到/opt下 一.下载m ...

  4. Hive表的基本操作

    目录 1. 创建表 2. 拷贝表 3. 查看表结构 4. 删除表 5. 修改表 5.1 表重命名 5.2 增.修.删分区 5.3 修改列信息 5.4 增加列 5.5 删除列 5.6 修改表的属性 1. ...

  5. JVM-03

    目录 1.1 新生代垃圾收集器 1.1.1 Serial 垃圾收集器(单线程) 1.1.2 ParNew 垃圾收集器(多线程) 1.1.3 Parallel Scavenge 垃圾收集器(多线程) 2 ...

  6. nodejs中的文件系统

    . 目录 简介 nodejs中的文件系统模块 Promise版本的fs 文件描述符 fs.stat文件状态信息 fs的文件读写 fs的文件夹操作 path操作 简介 nodejs使用了异步IO来提升服 ...

  7. Log4j日志记录

    1.导入log4j的jar包 2.写log4j.properties文件,配置日志记录参数,一般参数如下所示: 第二行指定了输出日志的目录,此处用的相对路径,也可换成绝对路径: 第三行指定了输出的记录 ...

  8. 攻防世界 - Web(一)

    baby_web: 1.根据题目提示,初始页面即为index,将1.php改为index.php,发现依然跳转成1.php,尝试修改抓包,出现如下回显, 2.在header中获取flag, flag: ...

  9. 鸿蒙的fetch请求加载聚合数据的前期准备工作-手动配置网络权限

    目录: 1.双击打开"config.json"文件 2.找到配置网络访问权限位置1 3.配置内容1 4.默认访问内容是空的 5.添加配置内容2 6.复制需要配置的网络二级URL 7 ...

  10. luogu P2198 杀蚂蚁

    题目描述 经过小FF的研究,他发现蚂蚁们每次都走同一条长度为n个单位的路线进攻, 且蚂蚁们的经过一个单位长度所需的时间为T秒.也就是说,只要小FF在条路线上布防且给蚂蚁造成沉痛伤害就能阻止蚂蚁的进军. ...