随笔 - 73  文章 - 0 评论 - 0 阅读 - 6680
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

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的子结点 
}
posted on   naiji  阅读(102)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示