luogu3379 【模板】最近公共祖先(LCA) 倍增法

题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

整体步骤:1.使两个点深度相同;2.使两个点相同。

这两个步骤都可用倍增法进行优化。定义每个节点的Elder[i]为该节点的2^k(或者说是二进制中的1,10,100,1000...)辈祖先。求它时要利用性质:cur->Elder[i]==cur->Elder[i-1]->Elder[i-1]。具体步骤看代码。

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

const int MAX_FA = 10, MAX_NODE = 500010, MAX_EDGE = MAX_NODE * 2;

struct Node;
struct Edge;

struct Node
{
	int Id, Depth;
	Edge *Head;
	Node *Elder[MAX_FA];
}_nodes[MAX_NODE], *Root;
int TotNode;

struct Edge
{
	Node *From, *To;
	Edge *Next;
}*_edges[MAX_EDGE];
int _edgeCnt;

void Init(int root, int totNode)
{
	_edgeCnt = 0;
	TotNode = totNode;
	Root = _nodes + root;
	memset(_nodes, 0, sizeof(_nodes));
}

Edge *NewEdge()
{
	return _edges[++_edgeCnt] = new Edge();
}

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

void Build(int uId, int vId)
{
	Node *u = _nodes + uId, *v = _nodes + vId;
	u->Id = uId;
	v->Id = vId;
	AddEdge(u, v);
	AddEdge(v, u);
}

int Log2(int x)
{
	int cnt = 0;
	while (x / 2)
	{
		cnt++;
		x /= 2;
	}
	return cnt;
}

void Dfs(Node *cur, Edge *FromFa)
{
	if(!FromFa)
		cur->Depth = 1;
	else
	{
		cur->Elder[0] = FromFa->From;
		cur->Depth = cur->Elder[0]->Depth + 1;
		for(int i=1; cur->Elder[i-1]->Elder[i-1]; i++)
			cur->Elder[i] = cur->Elder[i-1]->Elder[i-1];
	}
	for(Edge *e = cur->Head; e; e=e->Next)
		if(e->To!=cur->Elder[0])
			Dfs(e->To, e);
}

void DfsStart()
{
	Dfs(Root, NULL);
}

Node *Lca(Node *deep, Node *high)
{
	if (deep->Depth < high->Depth)
		swap(deep, high);
	int len = deep->Depth - high->Depth;
	for(int k=0; len; k++)
	{
		if((1 << k) & len)
		{
			deep=deep->Elder[k];
			len -= (1 << k);//把len二进制中当前的1去掉
		}
	}
	if (deep == high)
		return deep;
	for (int k = Log2(deep->Depth); k >= 0; k--)
	{
		if (deep->Elder[k] != high->Elder[k])
		{
			deep = deep->Elder[k];
			high = high->Elder[k];
		}
	}
	return deep->Elder[0];
}

int main()
{
	int totNode, totQ, rootId, uId, vId, id1, id2;
	scanf("%d%d%d", &totNode, &totQ, &rootId);
	Init(rootId, totNode);
	for (int i = 1; i < totNode; i++)
	{
		scanf("%d%d", &uId, &vId);
		Build(uId, vId);
	}
	DfsStart();
	for (int i = 1; i <= totQ; i++)
	{
		scanf("%d%d", &id1, &id2);
		printf("%d\n", Lca(id1 + _nodes, id2 + _nodes)->Id);
	}
	return 0;
}

  

对Lca中for循环正确性的解释:每个整数都可以表示为sum(2^k)。所以以此方式可以到达一个节点的任意辈祖先。

注意:

  • k初值有log。
  • 树的深度和高度要区分开来。
  • Dfs时一开始循环中的判断cur->[k-1]!=NULL是为了处理根节点。
posted @ 2018-02-20 11:00  headboy2002  阅读(178)  评论(0编辑  收藏  举报