Aizu 2677 tarjan离线求lca

给一个根为1的无权树,然后它要人工BFS这棵树,就是按BFS的顺序去走它,问一共要走多少步,假设一条边走一步

首先就BFS这个树,然后根据出队顺序得到我们走的顺序,假设为a,b,c,d,e,f,,,,然后,呃,a肯定是1,所以我们就是从1走到b,然后b到c,c到d,

所以我们要求他们之间的距离,树上两点的距离显然就是,d[u]+d[v]-2*d[lca(u,v)]。d[u]表示它的深度(走到根节点的路径长度),lca表示这两个点的最近公共祖先。

这个显然就是tarjan离线求lca了。讲解网上很多,大概就是根据dfs序边求边搞。

我这里只说一个问题就是,我们对于所有的查询,也是以树的形式保存下来了,在遍历完这个子树之后我们要进行对应的查询,比如u,v吧,我们把u子树操作完之后,就要查询v,但是这时候v可能访问或者没访问,如果访问了我们肯定就统计下来了,没访问呢,这就涉及到我们建这个树时是建一条边还是两条边了,不太清楚网上那些建一条边的是怎么解决的,可能有特殊的建边技巧或者数据有某种性质。反正我看到的解决办法就是建双向边。

它那个利用的是建一个vis数组表示是否完全访问,然后在处理完这个树的所有子树时就标记为true,然后我想说的一个问题它就解决了,就是假如v是u的子树里的,那么在处理v时,v操作完之后u的标记还是false,因为u还没操作完,但是u操作完之后v肯定是true的,所以这时就计算了。当然,假如不在一个子树就肯定是晚操作的那个节点进行计算。

但是我想节省些空间就没有建立vis数组,是通过判断它的并查集的根是否为0(初始值)来判断的,也是建立的双向边,这样假如v不在u的子树里肯定是没问题的,访问u时v还没操作,然后v操作那里u已经操作结束了,然后就会计算,但是如果v是u的子树,v操作完之后,因为u已经开始操作了,所以它的并查集已经有根了,然后就会计算一次,而在u操作完之后也会计算一次,后来想的一个折中的办法就是一开始时只建立一条单向边,假设为u到v,那么我在u操作完时要计算,假如v已经被处理了,肯定直接计算就可以,但是假如v没被处理,那就加一条v到u的边,这样后面再处理v时肯定就会计算u和v了,然后就解决了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <queue>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <set>
#include <map>
using namespace std;
#define ll long long
#define maxn 100005
vector<int> tree[maxn];
vector<int> query[maxn];
int father[maxn];
int depth[maxn];
ll ans = 0;
int findSet(int x)//路径压缩
{
	if (x == father[x])
		return x;
	else
		return father[x] = findSet(father[x]);
}
void unionSet(int x, int y)//把y并到x中去
{
	int fx = findSet(x);
	int fy = findSet(y);
	if (fy == fx)
		return;
	father[fy] = fx;
}
void bfs()
{
	queue<int> Q;
	Q.push(1);
	int last = 0;
	int t;
	while (!Q.empty())
	{
		t = Q.front();
		if (last != 0)
		{
			query[last].push_back(t);
		}
		last = t;
		Q.pop();
		for (unsigned int i = 0; i < tree[t].size(); ++i)
		{
			Q.push(tree[t][i]);
		}
	}
}
void Tarjan_LCA(int u)
{
	father[u] = u;
	int t;
	for (unsigned int i = 0; i < tree[u].size(); ++i)
	{
		t = tree[u][i];
		depth[t] = depth[u] + 1;
		Tarjan_LCA(t);
		unionSet(u, t);
	}
	for (unsigned int i = 0; i < query[u].size(); ++i)
	{
		t = query[u][i];
		if (father[t] != 0)
		{
			//printf("sum %d %d\n", u, t);
			ans += (ll)depth[u] + (ll)depth[t] - 2 * (ll)depth[findSet(t)];
		}
		else
		{
			query[t].push_back(u);
		}
	}
}
int main()
{
	//freopen("input.txt", "r", stdin);
	//freopen("output.txt", "w", stdout);
	int n;
	scanf("%d", &n);
	int tmp;
	for (int i = 2; i <= n; ++i)
	{
		scanf("%d", &tmp);
		tree[tmp].push_back(i);
	}
	bfs();
	/*for (int i = 1; i <= n; ++i)
	{
		for (unsigned int j = 0; j < query[i].size(); ++j)
			printf("%d ", query[i][j]);
		printf("\n");
	}*/
	Tarjan_LCA(1);
	/*for (int i = 1; i <= n; ++i)
		printf("%d ", depth[i]);
	printf("\n");*/
	printf("%lld\n", ans);
	//system("pause");
	//while (1);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值