trajan
求割点,桥,最近公共祖先(LCA)tarjan算法和倍增,
求割点:
P3388 【模板】割点(割顶) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
求桥:
P1656 炸铁路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
求桥和割点是一类问题:
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
struct edge
{
int x, y;
};
edge p;
int n, m, book[20004], dfn[20004], low[20004], cut[20004], fa[20004], t, top = 1;
vector<int> a[20004];
edge q[5005];
void dfs(int x);
int main()
{
cin >> n >> m;
int x, y;
for (int i = 1; i <= m; i++)
{
cin >> x >> y;
a[x].push_back(y); a[y].push_back(x);
}
t = 1;
for (int i = 1; i <= n; i++)
{
if (book[i] == 0)
{
fa[i] = -1;
dfs(i);
}
}
for (int i = 1; i <= n; i++)
cout << cut[i] << " ";
for (int i = 1; i < top; i++)
cout << q[i].x << " " << q[i].y << endl;
return 0;
}
void dfs(int x)
{
book[x] = 1;
dfn[x] = t; low[x] = t;
t++;
int len = a[x].size(), child = 0;
for (int i = 0; i < len; i++)
{
if (book[a[x][i]] == 0)
{
fa[a[x][i]] = x;
child++;
dfs(a[x][i]);
low[x] = min(low[x], low[a[x][i]]);
//if (fa[x] != -1 && low[a[x][i]] >= dfn[x])
cut[x] = 1;
if (fa[x] == -1 && child >= 2)
cut[x] = 1;//这几行代码是求割点
///if (low[a[x][i]] > dfn[x])
{
q[top].x = x; q[top].y = a[x][i];
top++;
}///求桥
}
else if (book[a[x][i]] != 0 && fa[x] != a[x][i])
low[x] = min(low[x], dfn[a[x][i]]);
}
}
最近公共祖先(LCA):
tarjan(离线)算法求Lca:(可能会被卡tle)
P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
1,将点与点的关系用邻接表的形式存起来,一次性读入所有的询问(trajan是离线算法)
将询问的点x和y和下标存起来
struct node
{
int index, end;
};
vector<node> que[500005];
for (int i = 1; i <= m; i++)
{
cin >> x >> y;
p.index = i; p.end = y;
que[x].push_back(p);
p.end = x;
que[y].push_back(p);
}
2,使用并查集,初始化
3,dfs先遍历x节点的所有子节点,遍历完后,查询LCA(x,y)中的y是否出现过,出现过则LCA(x,y)=find(y);
查询结束标记x节点访问过,且fa[x]=father;
先遍历,在查询,在标记
void f(int x, int father)
{
int len = a[x].size();
for (int i = 0; i < len; i++)
{
if(a[x][i]!=father)
f(a[x][i], x);
}
len = que[x].size();
for (int i = 0; i < len; i++)
{
if (book[que[x][i].end] == 1)
ans[que[x][i].index] = find(que[x][i].end);
}
fa[x] = father;
book[x] = 1;
}
倍增法求LCA
(21条消息) 倍增法求Lca(最近公共祖先)_姬小野的博客-CSDN博客_倍增求lca
思路:
1,找任意两个节点的LCA有两种情况:
- 两个节点的深度相同
- 两节点深度不同
算法实现来说, 第一种情况是第二种情况的特殊情况, 第二种情况是要转化成第一种情况的
转化:对于两个深度不同的结点, 把深度更深的那个向其父节点迭代, 直到这个迭代结点和另一个结点深度相同, 那么这两个深度相同的结点的Lca也就是原两个结点的Lca
这里使用的是倍增法
剩下的问题是求两个深度相同的节点的LCA,使用倍增法
复杂度:O(VlogV +QlogV) Q为查询次数,V代表结点数量
基本步骤
- 存储一棵树(邻接表法)
- 获取树各结点的上的深度和父亲(dfs或bfs)
- 获取2次幂祖先的结点, 用parents【maxn】【20】数组存储, 倍增法关键
- 用倍增法查询Lca
步骤一:
cin>>n;
for (int i = 1; i < n; i++)
{
cin >> x >> y;
a[x].push_back(y);
a[y].push_back(x);
}
步骤二:
dep[root]=1;
parent[root][0]=root;
void dfs(int x)
{
int len = a[x].size();
for (int i = 0; i < len; i++)
{
if (a[x][i] != fa[x])
{
fa[a[x][i]] = x;
dep[a[x][i]] = dep[x] + 1;
dfs(a[x][i]);
}
}
}
步骤三:
求祖先
parent数组的意义:他存的是节点x的二次幂的祖先。(倍增法的思想)
parents[i][j] = parents[parents[i][j-1]][j-1];
x节点的向上2^j的祖先是 x的向上2^(j-1)节点 的向上2^(j-1)节点的祖先
即:x节点向上2^10=(x节点向上2^9的那个节点)的2^9
void getparent()
{
for (int up = 1; (1 << up) <= n; up++)
{
for (int i = 1; i <= n; i++)
{
parent[i][up] = parent[parent[i][up - 1]][up - 1];
}
}
}
步骤四:
做完了前面O(VlogV)的预处理操作, 剩下的就是查询了, 一次查询O(logV)
int LCA(int u, int v)
{
if (dep[u] < dep[v])//使u的深度更大,便于后面操作
swap(u, v);
int i = -1, j;
while ((1 << (i + 1)) <= dep[u])
i++;
//对于这个i恰好是 , 2^i<=dep[u],而2^(i+1)>dep[u]
//即,u一次最大能往上跳2^i
for (j = i; j >= 0; j--) //这个循环是为了让u和v到同一深度
{
if (dep[u] - (1 << j) >= dep[v])
u = parent[u][j];
}
if (u == v) // 刚好是祖宗
return u;
for (int j = i; j >= 0; j--) // u和v一起二分找祖宗
{
if (parent[u][j] != parent[v][j])
{
u = parent[u][j];
v = parent[v][j];
}
}
return parent[u][0];// 说明上个循环迭代到了Lca的子结点
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话