先放模板有时间补充:
算法的在线与离线
离线算法
算法设计策略都是基于在执行算法前输入数据已知的基本假设,也就是说,对于一个离线算法,在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果,通常将这类具有问题完全信息前提下设计出的算法成为离线算法(Offline Algorithms)
在线算法
在计算机科学中,一个在线算法是指它可以以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入。相对的,对于一个离线算法,在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果。例如,选择排序在排序前就需要知道所有待排序元素,然而插入排序就不必。
因为在线算法并不知道整个的输入,所以它被迫做出的选择最后可能会被证明不是最优的,对在线算法的研究主要集中在当前环境下怎么做出选择。对相同问题的在线算法和离线算法的对比分析形成了以上观点。如果想从其他角度了解在线算法可以看一下 流算法(关注精确呈现过去的输入所使用的内存的量),动态算法(关注维护一个在线输入的结果所需要的时间复杂度)和在线机器学习。
本段文字摘录与网络,但原作者链接访问不了了
【原作者】 【转载】
tarjan 算法求解 LCA
#include <cstdio>
struct edge
{
int nex, to;
} e[2001], l[2001];
int head[1001], lis[1001], tot1, tot2, f[1001], n, m, q;
bool vis[1001];
int find(int x)
{
return ((x == f[x]) ? x : find(f[x]));
}
void merge(int a, int b)
{
if((a = find(a)) == (b = find(b))) return;
f[b] = a;
}
inline void adde(int a, int b)
{
e[++tot1].nex = head[a];
e[tot1].to = b;
head[a] = tot1;
e[++tot1].nex = head[b];
e[tot1].to = a;
head[b] = tot1;
}
inline void addq(int a, int b)
{
l[++tot2].nex = lis[a];
l[tot2].to = b;
lis[a] = tot2;
l[++tot2].nex = lis[b];
l[tot2].to = a;
lis[b] = tot2;
}
inline void tarjan_lca(int x)
{
vis[x] = 1;
for (register int i = head[x]; i; i = e[i].nex)
{
if (vis[e[i].to]) continue;
tarjan_lca(e[i].to);
f[e[i].to] = x;
}
for (register int i = lis[x]; i; i = l[i].nex)
{
if (vis[l[i].to])
{
// find(l[i].to) is the LCA
printf("%d<-->%d: %d\n", x, l[i].to, find(l[i].to));
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int x, y, i = 1; i <= m; ++i)
{
scanf("%d%d", &x, &y);
adde(x, y);
}
for (int x, y, i = 1; i <= q; ++i)
{
scanf("%d%d", &x, &y);
addq(x, y);
}
for (int i = 1; i <= n; ++i)
{
f[i] = i;
}
tarjan_lca(1);
}
/*
Input:
9 8 4
1 2
2 4
2 5
5 7
5 8
7 9
1 3
3 6
9 8
4 6
7 5
5 3
Ans: (not output)
9 <--> 8 = 5
4 <--> 6 = 1
7 <--> 5 = 5
5 <--> 3 = 1
*/
倍增求解 LCA
普通的基本知识
倍增,是分治的一种算法。每次将问题分成完全独立的两半,利用两半的关系求解。
比如,快速幂就是一种倍增。
倍增的基本时间复杂度是 O(logn)。
普通倍增算法
下面用两张图解释:求 u 和 v 的最近公共祖先(root)
首先,先用 dfs(或者 bfs 等)算出每个点的深度(dep[i])、祖先节点信息(f[i][j] 表示 i 节点的第 2j 个祖先节点)。
之后,我们发现 u 和 v 节点的深度不同,同时明显可以得出,二者的 LCA 的深度一定大于等于 4。于是 u、v 的 LCA 可以转化为 u、t 的 LCA。而 v 和 t 的深度相差 13,因此我们跳跃 3 次,分别跳 8、4、1 层(每次跳跃的层数是不大于深度差的最大的、且只有 2 作为约数的数)。
现在,问题已经转化为 u 和 t 的 LCA 了。此时我们再次倍增,先可以访问到 f[t][1](绿色节点)和 f[u][1](紫色节点)发现此时 f[t][1] ≠ f[u][1],说明二者的 LCA 的深度比比 2 大。于是我们让新的 f[u][1] 代替 u、新的 f[t][1] 代替 t,求 f[u][1] 和 f[t][1] 的 LCA。以此类推,直到二者相等为止。
但是如果相等就结束了吗?其实不是。倍增是从远到进倍增的,当前所求的解一定是公共祖先,但不一定是最近公共祖先。于是相等时我们不能退出循环,要继续搜索,直到循环结束,才得到我们目标节点,该节点的父节点就是 LCA(最后 f[u][0] = f[t][0],因此 f[u][0] 即 u 的父节点才是 LCA)。
模板代码
#include <cstdio>
#define N 100001
#define M 500001
#define swap(a, b) ({(a) ^= (b); (b) ^= (a); (a) ^= (b);})
// 基本代码
struct edge
{
int to, nex;
} e[M << 1];
// dep[i] 表示 i 号节点的深度
// f[i][j] 表示 i 号节点的第 2 ^ j 个祖先
// 特别地,f[i][0] 表示 i 号节点的父节点
// 因为 2 ^ (j - 1) + 2 ^ (j - 1) = 2 ^ j,
// 所以一个节点的 2 ^ j 代的祖先是其 2 ^ (j - 1) 代的祖先的第 2 ^ (j - 1) 个的祖先
// 于是有: f[i][j] = f[f[i][j - 1]][j - 1]
// 附:f[N][L],L 取 30 即可
// log[i] 存储 log2(i) 的下取整 int 值
// 但不需要调用 log2(int) 函数,递推可求
// 注意:log[L],L = N
int tot, head[N], vis[N], dep[N], f[N][31], log[N];
int n, q, a, b;
inline void add(int x, int y)
{
e[++tot].nex = head[x];
e[tot].to = y;
head[x] = tot;
e[++tot].nex = head[y];
e[tot].to = x;
head[y] = tot;
}
// 深搜获取节点深度及祖先信息
// x 表示目标节点,pre 表示父节点
void dfs(int x = 1, int pre = 0)
{
// 已经得到深度及信息
if (vis[x]) return;
vis[x] = 1;
dep[x] = dep[pre] + 1;
f[x][0] = pre;
// 因为 x 的 2 ^ i 级祖先最多是根节点
// 所以 2 ^ i <= dep[x],推出:i <= log2(dep[x])
// 可以打表储存,log[i] = log(i)
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].next)
dfs(e[i].to, x);
}
// 打表求 log[]
void mklog()
{
for (int i = 2; i < N; ++i)
log[i] = log[i / 2] + 1;
}
int LCA(int x, int y)
{
// 不妨设 dep[x] >= dep[y]
// 则先要让 y 跳到与 x 节点同样深度的父节点
if (dep[x] < dep[y])
swap(x, y);
while (dep[x] != dep[y])
// dep[x] - dep[y] 是高度差
// 比如深度相差 l,每次跳 k 阶
// 满足 k = 2 ^ a 且 k <= dep[x] - dep[y], 2 ^ (a + 1) > dep[x] - dep[y]
x = f[x][log[dep[x] - dep[y]]];
// 调整深度后两点相遇
if (x == y) return x;
// 逆向思考:如何在 a、b 不相遇的情况下跳到尽可能高的位置
// 该位置的父节点即为 LCA
// 从可能跳的最大步数 log[dep[x]], 开始,不断减小步数到 0
// 此处用 k 的原因是 k 表示幂
for (int k = log[dep[x]]; k >= 0; --k)
// 未找到
if (f[x][k] != fa[y][k])
{
// 跳跃
x = f[x][k];
y = f[y][k];
}
// 如果找到一点满足要求,该点可能不是最近的
// 因此需要继续减半查找
// 搜索结束,当前点的父节点即为 LCA
return f[x][0];
}
int main()
{
// 读入树
// 按无向图方法存边
mklog();
// 对于一棵树,任意节点可看做根节点
// 这里用 1 做根节点
dfs();
// 读入问题
// LCA(x, y) 即为最近公共祖先
// 附:x、y 两点距离:dep[a] + dep[b] - 2 * dep[LCA(a, b)]
}
ST 表前置知识——RMQ
ST 表详解
作者:Rotch
日期:2021-05-13
修改:2020-05-17