*【整理】一些基础板子
一些基础板子,不打算新开一篇学习笔记,就都整理在这里。
Ⅰ.LCA
1.倍增
预处理和单次查询的复杂度分别为
倍增预处理 $i 的
查询
- 易错点:若采用下面代码写法,根节点深度应设为
而非 ,否则若 为根节点,因 没有初始化,故当 时 ,且 ,导致 。
#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.欧拉序
欧拉序有以下两条性质:
- 任意两点简单路径上所有节点均在它们的欧拉序之间出现。
- 任意两点欧拉序之间不会出现它们 LCA 子树外的点。
使用 ST 表,设
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 序
考虑树上的两个结点
不妨设
当
根据性质,任何
考察
这意味着什么?我们只需要求在
这样做的正确性依赖于在 DFS 序
根据假设,此时
对于情况 1,
综上,若
预处理 ST 表的复杂度仍为
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.树链剖分
树剖求 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 的思想。我们需要从根结点去遍历这棵树。
当遍历到某一个结点(称之为
-
将当前结点标记为已经访问。
-
递归遍历所有它的子节点(称之为
),并在递归执行完后用并查集合并 和 。 -
遍历与当前节点有查询关系的结点(称之为
)(即是需要查询 LCA 的另一些结点),如果 已经访问,那么 与 的 LCA 就是 find(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/jiangchen4122/p/17641190.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步