LCA 最近公共祖先

本篇博客总结一下LCA问题及其离线算法tarjan,并辅以模板以助记忆。

所谓LCA问题就是查询两个节点的最近公共祖先,tarjan算法利用深度优先检索和并查集来解决这一问题,对于所有的查询,可在一次dfs的过程中求解。

具体的tarjan算法看这篇博客,这位博主写得已经非常完备了:

最近公共祖先LCA(Tarjan算法)的思考和算法实现



看完了tarjan的工作过程再看一下它的代码实现:
我用这道题来展示一下LCA的模板:首先找到树的根,然后套用tarjan算法。至于为什么tarjan算法可行,上面那篇博文里没讲,我这里在注释中给出了粗略的证明,简单地说就是 刚刚好访问两个节点时,这两个节点的LCA必定没有 在DFS中回溯,正是因为没有回溯,使得find(v)可以定位到它。
#include <iostream>
#include <vector>
#include <fstream>
#define M 100009
using namespace std;

int f[M];
int root;
int degree_in[M];
bool vis[M];
vector<int> tree[M];
vector<int> query[M];

int find(int x)
{
	return x == f[x] ? f[x] : f[x] = find(f[x]);   //并查集的路径压缩操作
}


void tarjan(int u)
{
	for (int i = 0; i < tree[u].size(); i++)//dfs
	{
		int v = tree[u][i];
		tarjan(v);
		f[v] = u;   //v搜索完,将v合并到它父亲的集合u中
	}
	vis[u] = 1;
	for (int i = 0; i < query[u].size(); i++)
	{
		int v = query[u][i];
		if (vis[v])     //v被搜索过
		{
			cout << find(v) << endl;    //寻找v所在的集合,即为最近公共祖先
		}                               
        /*证明:设lca为u和v的最近公共祖先,v和u必然分别在lca的两个不同的子树中(否则必然能找到更接近u、v的LCA),根据dfs性质,
        u还没有完成dfs,因此lca也没有完成dfs,f[lca]=lca依然成立,而v所在的子树搜索已经完成,因此其f已经全部更新,通过find函数一级级向上找,
        最终由于f[lca] = lca而停止,证毕。*/
	}
}

void init(int n)
{
	for (int i = 0; i <= n; i++)
	{
		vis[i] = false;
		tree[i].clear();
		query[i].clear();
		degree_in[i] = 0;
		f[i] = i;
	}
}

int main()
{
	//fstream cin("test.txt");
	int k;
	cin >> k;
	while (k--)
	{
		int n;
		cin >> n;
		init(n);
		for (int i = 0; i < n - 1; i++)
		{
			int a, b;
			cin >> a >> b;
			tree[a].push_back(b);
			degree_in[b]++;
		}
		for (int i = 1; i <= n; i++)
		{
			if (degree_in[i] == 0)
				root = i;
		}
		int a, b;
		cin >> a >> b;
		query[a].push_back(b);
		query[b].push_back(a);
		tarjan(root);
	}
	//system("pause");
	return 0;
}
poj1986  Distance Queries
这道题复杂一点,其本身是一个无向带权图,可以当成树来做,不是让你直接求LCA,而是要两个点之前的距离。

用dis[]来表示到root的距离,则a、b之间的距离就等于dis[ a ] + dis[ b ] — 2*dis[ LCA(a,b) ]

#include <iostream>
#include <fstream>
#include <vector>
#include <cstdio>
using namespace std;

struct node {
	int num, dis; //dis(index for query)
	node(int a, int b) :num(a), dis(b) {};
};

vector<node> tree[40005], query[40005];
int ans[10005];
int dis[40005];
int father[40005];
bool vis[40005];
int find(int x)
{
	if (x != father[x])
		father[x] = find(father[x]);
	return father[x];
}


void tarjan(int u, int len)
{
	dis[u] = len;
	father[u] = u;
	vis[u] = 1;
	for (int i = 0; i < query[u].size(); i++)
	{
		int v = query[u][i].num;
		if (vis[v])
		{
			int index = query[u][i].dis;
			int lca = find(v);
			ans[index] = dis[u] + dis[v] - 2 * dis[lca];
		}
	}
	for (int i = 0; i < tree[u].size(); i++)
	{
		if (!vis[tree[u][i].num])
		{
			int v = tree[u][i].num;
			int dis = tree[u][i].dis;
			tarjan(v, len + dis);
			father[v] = u;
		}
	}
	
}
int main()
{
	//fstream cin("test.txt");
	int n, m;
	scanf("%d%d", &n, &m);
	int a, b, dis;
	char ch;
	for (int i = 0; i < m; i++)
	{
		
		scanf("%d%d%d %c", &a, &b, &dis, &ch);
		tree[a].push_back(node(b, dis));
		tree[b].push_back(node(a, dis));
	}
	int q;
	scanf("%d", &q);
	
	for (int i = 0; i < q; i++)
	{
		
		scanf("%d%d", &a, &b);
		query[a].push_back(node(b, i));
		query[b].push_back(node(a, i));
	}
	tarjan(1, 0);
	for (int i = 0; i < q; i++)
		printf("%d\n", ans[i]);
	//system("pause");
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值