我需要一个答案|

Aurora-JC

园龄:3年粉丝:3关注:4

2023-08-27 18:04阅读: 20评论: 0推荐: 0

*【整理】一些基础板子

一些基础板子,不打算新开一篇学习笔记,就都整理在这里。

Ⅰ.LCA

1.倍增

预处理和单次查询的复杂度分别为 O(nlogn)O(logn),空间 O(nlogn)

倍增预处理 $i 的 2k 级祖先,记为 fi,k,求出每个节点的深度 di

查询 u,v LCA 时,先将 u,v 跳到同一深度,再 同时 向上跳到 u,v 深度最小的祖先 ancu,ancv 满足 ancuancv,则 fa(ancu)=fa(ancv)=lca(u,v)。注意特判 u,v 跳到同一深度后 u=v 的情况,此时原 u,v 为祖先后代关系。

  • 易错点:若采用下面代码写法,根节点深度应设为 1 而非 0,否则若 v 为根节点,因 dep0 没有初始化,故当 2kdepudep(fu,k)=dep(f0)=0,且 depv=0,导致 ufu,k=0
#include<bits/stdc++.h>
#define N 500005
#define INF 0x3f3f3f3f
using namespace std;
int fa[N],d[N];
int Head[N],to[N*2],Next[N*2];
int f[N][25];
int n,m,t,s,tot;
void add(int u,int v){
to[++tot]=v;
Next[tot]=Head[u];
Head[u]=tot;
}
void bfs(int s){
queue<int> q;
q.push(s);
d[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=Head[x];i;i=Next[i]){
int y=to[i];
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%d",&n,&m,&s);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
t=(int)(log(n)/log(2))+1;
bfs(s);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}

2.欧拉序

O(nlogn)O(1),空间 O(nlogn),有两倍常数。

欧拉序有以下两条性质:

  • 任意两点简单路径上所有节点均在它们的欧拉序之间出现。
  • 任意两点欧拉序之间不会出现它们 LCA 子树外的点。

使用 ST 表,设 fk,i 表示欧拉序 ii+2k1 位置上 深度 最小的节点,查询可做到 O(1)

int ol[N << 1][21], lg[N << 1], pos[N], d[N];
void dfs0(int x, int f){
ol[++cnt][0] = x, pos[x] = cnt, d[x] = d[f] + 1;
for(int i = Head[x]; i; i = Next[i]){
int y = to[i]; if(y == f) continue;
dfs0(y, x), ol[++cnt][0] = x;
}
}
int get_min(int a, int b){
return d[a] < d[b] ? a : b;
}
void get_ol(){
for(int i = 2; i <= cnt; ++i) lg[i] = lg[i >> 1] + 1;
for(int t = 1; (1 << t) <= cnt; ++t)
for(int i = 1; i + (1 << t) <= cnt; ++i)
ol[i][t] = get_min(ol[i][t - 1], ol[i + (1 << t - 1)][t - 1]);
}
int lca(int x, int y){
if(pos[x] > pos[y]) swap(x, y);
int xx = pos[x], yy = pos[y], len = yy - xx + 1;
int lca = get_min(ol[xx][lg[len]], ol[yy - (1 << lg[len]) + 1][lg[len]]);
return lca;
}

3.dfs 序

考虑树上的两个结点 u,v 及其最近公共祖先 d,我们不得不使用欧拉序求 LCA 的原因是在欧拉序中,du,v 之间出现过,但在 DFS 序中,d 并没有在 u,v 之间出现过。对于 DFS 序而言,祖先一定出现在后代之前(性质)。

不妨设 u 的 DFN 小于 v 的 DFN(假设)。

u 不是 v 的祖先 时(情况 1),DFS 的顺序为从 d 下降到 u,再回到 d,再往下降到 v

根据性质,任何 d 以及 d 的祖先均不会出现在 uv 的 DFS 序中。

考察 dv 方向上的第一个结点 v,即设 vd 的 / 子树包含 v 的 / 儿子。根据 DFS 的顺序,显然 vuv 的 DFS 序之间。

这意味着什么?我们只需要求在 u 的 DFS 序和 v 的 DFS 序之间深度最小的任意一个结点,那么 它的父亲 即为 u,v 的 LCA。

这样做的正确性依赖于在 DFS 序 uv 之间,d 以及 d 的祖先必然不会存在,且必然存在 d 的儿子。

u,v 成祖先后代关系(情况 2)是容易判断的,但这不优美,不能体现出 DFS 求 LCA 的优势:简洁。为了判断还要记录每个结点的子树大小,但我们自然希望求 LCA 的方法越简单越快越好。

根据假设,此时 u 一定是 v 的祖先。因此考虑令查询区间从 [dfnu,dfnv] 变成 [dfnu+1,dfnv]

对于情况 1,u 显然一定不等于 v,所以情况 2 对于算法进行的修改仍然适用于情况 1。

综上,若 uv,则 u,v 之间的 LCA 等于在 DFS 序中,位置在 dfnu+1dfnv 之间的深度最小的结点的父亲。若 u=v,则它们的 LCA 就等于 u,这是唯一需要特判的情况。

预处理 ST 表的复杂度仍为 O(nlogn),但常数减半。

int get_min(int x, int y){return dep[x] < dep[y] ? x : y;}
void dfs(int x, int ff){
dfn[x] = ++dn, dep[x] = dep[ff] + 1, mi[0][dn] = ff;
for(auto y : e[x]) if(ff != y) dfs(y, x);
}
int lca(int u, int v){
if(u == v) return u;
if((u = dfn[u]) > (v = dfn[v])) swap(u, v);
int d = lg[v - u++];
return get_min(mi[d][u], mi[d][v - (1 << d) + 1]);
}
int main(){
dfs(1, 0);
for(int i = 2; i <= n; ++i) lg[i] = lg[i >> 1] + 1;
for(int i = 1; i <= lg[n]; ++i)
for(int j = 1; j + (1 << i) - 1 <= n; ++j)
mi[i][j] = get_min(mi[i - 1][j], mi[i - 1][j + (1 << i - 1)]);
return 0;
}

4.树链剖分

O(n)O(logn),空间 O(n)

树剖求 LCA 的空间复杂度更优,可配合需要树剖的题目使用。

查询 u,v 的 LCA 时,只要 u,v 不在同一重链,设 u 重链顶端的深度不小于 v 重链顶端的深度,则令 u 变为其重链顶的父亲。最终 u,v 在同一重链,深度较小的节点即原 u,v LCA。

int LCA(int x, int y){
while(top[x] != top[y]){
if(d[top[x]] < d[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(d[x] > d[y]) swap(x, y);
return x;
}

5.Tarjan

遍历每一个结点并使用并查集记录父子关系。

Tarjan 是一种 DFS 的思想。我们需要从根结点去遍历这棵树。

当遍历到某一个结点(称之为 x) 时,你有以下几点需要做的。

  1. 将当前结点标记为已经访问。

  2. 递归遍历所有它的子节点(称之为 y),并在递归执行完后用并查集合并 xy

  3. 遍历与当前节点有查询关系的结点(称之为 z)(即是需要查询 LCA 的另一些结点),如果 z 已经访问,那么 xz 的 LCA 就是 find(z)
    (即 z 回溯过的深度最小的祖先(想一下显然这就是LCA)),记录下来就可以了。

#include<bits/stdc++.h>
#define N 500005
using namespace std;
int read(){
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f = -f; ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return x * f;
}
int n, m, rt, tot;
int Head[N], to[N<<1], Next[N<<1];
int fa[N], ans[N], vis[N];
vector<int> q[N], q_id[N];
int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
void add(int u, int v){
to[++tot] = v, Next[tot] = Head[u], Head[u] = tot;
}
void tarjan(int x){
vis[x] = 1;
for(int i = Head[x];i ;i = Next[i]){
int y = to[i];
if(vis[y]) continue;
tarjan(y); fa[y] = find(x);
}
for(int i = 0; i < q[x].size(); ++i){
int y = q[x][i], id = q_id[x][i];
if(vis[y]) ans[id] = find(y);
}
}
signed main(){
n = read(), m = read(), rt = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read();
add(u, v), add(v, u);
}
for(int i = 1; i <= n; ++i) fa[i] = i;
for(int i = 1; i <= m; ++i){
int u = read(), v = read();
q[u].push_back(v), q[v].push_back(u);
q_id[u].push_back(i), q_id[v].push_back(i);
}
tarjan(rt);
for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
return 0;
}

*6.The Method of Four Russians

Ⅱ.并查集

int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
int main(){
for(int i = 1; i <= n; ++i) fa[i] = i;
}

Ⅲ.快速幂

int qsm(int a, int b){
int res = 1;
for(; b; b >>= 1, a = a * a % mod) if(b & 1) res = res * a % mod;
return res;
}

Ⅳ.线性筛

void init(){
for(int i = 2; i <= 1e7; ++i){
if(!v[i]) prim[++tot] = i;
for(int j = 1; j <= tot&& 1ll * i * prim[j] <= 1e7; ++j){
v[i * prim[j]] = 1;
if(i % prim[j] == 0) break;
}
}
}

Ⅴ.ST表

lg[1] = 0;
for(int i = 2; i <= n; ++i) lg[i] = lg[i >> 1] + 1;
void ST_prework(){
for(int i = 1; i <= n; ++i) f[i][0] = a[i];
int t = log(n) / log(2) + 1;
for(int j = 1; j < t; ++j)
for(int i = 1; i <= n - (1 << j) + 1; ++i)
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
int ST_query(int l, int r){
int k = lg[r - l + 1];
return max(f[l][k], f[r - (1 << k) + 1][k]);
}

Ⅵ.三分法

while(l + eps < r){
db len = (r - l) / 3.0;
db midl = l + len, midr = r - len;
if(f(midl) > f(midr)) ans = midl, r = midr;
else l = midl;
}

参考资料:https://www.cnblogs.com/alex-wei/p/Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree_Tree.html

本文作者:南风未起

本文链接:https://www.cnblogs.com/jiangchen4122/p/17641190.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Aurora-JC  阅读(20)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起