LCA专题

标签(空格分隔): LCA


我的个人网站挂了,最近就先用这个来写博客吧。以后争取在这个网站写一些与OI无关的个人爱好的东西。


题目来源:code[VS]

倍增--在线算法

用 $f[i][j]$ 记录从 $i$ 向上跳 $2^j$ 次会跳到的位置。需 $O(nlog(n))$ 的预处理与 $O(mlog(n))$ 的查询。具体如下:

//code[VS]	P1036	LCA
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

struct  Edge
{
	int from,to,next;
	bool access;
	Edge(int form=0,int to=0,int next=0,bool access=true):from(from),to(to),next(next),access(access)	{}
}e[60100]; 

int depth[30100],f[30100][25],v[25],pre[30100];
int n;

int c[30100];
void bfs(int s)
{
	c[1] = s;	depth[s] = 1;
	int head = 1,tail = 1;
	while (head<=tail)
	{
		int x = c[head++];
		int v = pre[x];
		while (v)
		{
			if (e[v].access)
			{
				depth[e[v].to] = depth[x] + 1;
				c[++tail] = e[v].to;
				f[e[v].to][0] = x;
				e[v^1].access = false;
			}
			v = e[v].next;
		}
	}
}

void prepare()
{
	v[0] = 1;
	for (int j = 1; j<=20; j++)
		for (int i = 1; i<=n; i++)
		{
			f[i][j] = f[f[i][j-1]][j-1];
			v[j] = 2*v[j-1];
		}
}

int LCA(int x,int y)
{

	int ans=0;
	if (depth[x] < depth[y])	swap(x,y);

	for (int i = 20;depth[x]>depth[y];i--)
		if (depth[f[x][i]] >= depth[y])
		{
			ans += v[i];
			x = f[x][i];
		}

	if (x==y)	return ans;

	for (int i = 20; i>=0; i--)
		if (f[x][i] != f[y][i])
		{
			x = f[x][i];
			y = f[y][i];
			ans += v[i]*2;
		}
	ans += 2;
	return ans;
}

int main()
{
	memset(pre,0,sizeof(pre));

	scanf("%d",&n);
	for (int i = 1; i<n; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		e[2*i] = Edge(x,y,pre[x],true);
		pre[x] = 2*i;
		e[2*i+1] = Edge(y,x,pre[y],true);
		pre[y] = 2*i+1;
	}

	bfs(1);

	prepare();

	int m;
	scanf("%d",&m);
	int x,y=1,ans=0;
	for (int i = 1; i<=m; i++)
	{
		x = y;
		scanf("%d",&y);
		ans += LCA(x,y);
	}	

	printf("%d",ans);
}

Tarjan--离线算法

对于这么一个玄学的算法,我不想说太多。。。用并查集进行维护,可以证明,每当搜到 $x$ 时,与之对应的 $y$ 所在集合的祖先一定为这两点的LCA。

//code[VS]	P1036	LCA
#include <cstdio>
#include <cstring>

struct Edge
{
	int from,to,next;
	bool access;
	Edge(int from=0,int to=0,int next=0,bool access=true):from(from),to(to),next(next),access(access)	{}
}e[60100];

struct Query
{
	int point,next;
	Query(int point=0,int next=0):point(point),next(next)	{}
}q[60100];

//fa[i]记录i的父亲,f[i]记录i指向的第一条边,fq[i]记录i指向的第一个查询
int fa[30100],f[30100],fq[30100],depth[30100];
int ans=0;		//记录答案
bool b[30100];

int c[30100];
void bfs(int s)		//通过广搜计算出深度与边的方向
{
	c[1] = s;	depth[s] = 1;
	int head = 1,tail = 1;
	while (head<=tail)
	{
		int x = c[head++];
		int v = f[x];
		while (v)
		{
			if (e[v].access)
			{
				e[v^1].access = false;			//将该边的反向边设为false
				depth[e[v].to] = depth[x] + 1;
				c[++tail] = e[v].to;
			}
			v = e[v].next;
		}
	}
}

int find(int x)
{
	return x==fa[x]?x:fa[x]=find(fa[x]);
}

void Union(int x,int y)
{
	int fy = find(y);
	fa[fy] = x;
}

void Tarjan_LCA(int x)
{
	fa[x] = x;			//以x创建一个集合
	int v = f[x];
	while (v)			//循环x的临边
	{
		if (e[v].access)	//如果该边为正方向(即指向儿子)
		{
			Tarjan_LCA(e[v].to);
			Union(x,e[v].to);		//将x的子树与x合并
		}
		v = e[v].next;
	}
	b[x] = true;	//设置该点已走过(必须在处理完儿子后设置,否则会有重复计算)

	v = fq[x];
	while (v)			//处理关于x点的查询
	{
		if (b[q[v].point])			//如果另一点已走过 花费=a点深度+b点深度-2*LCA(a,b)的深度
			ans = ans + ( depth[x] + depth[q[v].point] - 2*depth[find(q[v].point)] );
		v = q[v].next;
	}
}

int main()
{
	memset(b,false,sizeof(b));
	memset(f,0,sizeof(f));
	memset(fq,0,sizeof(fq));

	int n;
	scanf("%d",&n);
	for (int i = 1; i<n; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		e[2*i] = Edge(x,y,f[x],true);	
		f[x] = 2*i;
		e[2*i+1] = Edge(y,x,f[y],true);
		f[y] = 2*i+1;
	}

	int m;
	scanf("%d",&m);
	int x,y=1;
	for (int i = 1; i<=m; i++)		//因为不知先查询到哪个点所以要存储双向变
	{
		x = y;
		scanf("%d",&y);
		q[i*2-1] = Query(y,fq[x]);
		fq[x] = 2*i-1;
		q[2*i] = Query(x,fq[y]);
		fq[y] = 2*i;
	}

	bfs(1);

	Tarjan_LCA(1);

	printf("%d",ans);
}
posted @ 2016-11-09 20:34  OI_songer  阅读(207)  评论(0编辑  收藏  举报