luogu3379 【模板】最近公共祖先(LCA) Tarjan

  LCA的Tarjan算法是一个离线算法,复杂度$O(n+q)$。

  我们知道Dfs搜索树时会形成一个搜索栈。搜索栈顶节点cur时,对于另外一个节点v,它们的LCA便是v到根节点的路径与搜索栈开始分叉的那个节点lca。而站在cur上枚举v找lca的过程可以用并查集优化到$O(\log n)$级别。

  并查集的定义:规定v为已经搜索且已经回溯,当前搜索栈顶为cur,则v并查集中的Father为LCA(cur,v)。查询可直接运用该定义。

  并查集的维护:每当搜索栈顶弹出一个节点x时,将x在并查集中的Father设为其在树中的Father。这样x及x的子树的Father就都是这个栈内节点x->Father了。

 

  注意,不要用vector,全部用邻接表,否则慢。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int MAX_NODE = 500010, MAX_PATH = 500010;

struct Node;
struct Edge;
struct Path;
struct Link;

struct Node
{
	int DfsN;
	Edge *Head;
	Node *UnFa;//UnionFather
	Node *Father;
	Link *HeadLink;
}_nodes[MAX_NODE], *Root;
int TotNode;

struct Edge
{
	Node *To;
	Edge *Next;
}_edges[MAX_NODE * 2];
int _eCount;

struct Path
{
	Node *From, *To;
	Node *Lca;
}_paths[MAX_PATH];
int TotPath;

struct Link
{
	Node *To;
	Path *Query;
	Link *Next;
}_links[MAX_PATH * 2];
int LinkCnt;

void AddEdge(Node *from, Node *to)
{
	Edge *e = _edges + ++_eCount;
	e->To = to;
	e->Next = from->Head;
	from->Head = e;
}

void AddLink(Node *from, Node *to, Path *query)
{
	Link *cur = _links + ++LinkCnt;
	cur->To = to;
	cur->Query = query;
	cur->Next = from->HeadLink;
	from->HeadLink = cur;
}

void InitAllPath()
{
	for (int i = 1; i <= TotPath; i++)
	{
		AddLink(_paths[i].From, _paths[i].To, _paths + i);
		AddLink(_paths[i].To, _paths[i].From, _paths + i);
	}
}

Node *GetRoot(Node *cur)
{
	return cur->UnFa == cur ? cur : cur->UnFa = GetRoot(cur->UnFa);
}

void Tarjan(Node *cur, Node *fa)
{
	cur->DfsN = 1;
	cur->UnFa = cur;
	cur->Father = fa;
	for (Edge *e = cur->Head; e; e = e->Next)
	{
		if (e->To == cur->Father)
			continue;
		Tarjan(e->To, cur);
		e->To->UnFa = cur;
	}
	for (Link *link = cur->HeadLink; link; link = link->Next)
		if (link->To->DfsN == 2)
			link->Query->Lca = GetRoot(link->To);
	cur->DfsN = 2;
}

int main()
{
	int rootId;
	scanf("%d%d%d", &TotNode, &TotPath, &rootId);
	Root = _nodes + rootId;
	for (int i = 1; i <= TotNode - 1; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		AddEdge(_nodes + u, _nodes + v);
		AddEdge(_nodes + v, _nodes + u);
	}
	for (int i = 1; i <= TotPath; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		_paths[i].From = _nodes + u;
		_paths[i].To = _nodes + v;
	}
	InitAllPath();
	Tarjan(Root, NULL);
	for (int i = 1; i <= TotPath; i++)
		printf("%lld\n", _paths[i].Lca - _nodes);
	return 0;
}

  

转载于:https://www.cnblogs.com/headboy2002/p/9690555.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值