zoukankan      html  css  js  c++  java
  • 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);
    }
    
  • 相关阅读:
    php一些技巧函数
    让apache不区分图片和文件后缀大小写
    ajax的同步和异步问题 (转)
    windows上在linux客户端上传小文件lrzsz
    linux查看时间和修改时间
    mysql常用命令
    yum安装 lnmp
    linux网卡设置详解
    centos最小安装,之后想安装x-windows,安装图形界面 startx
    optimize table table_name myisam mysql自动清除删除过留下的空记录
  • 原文地址:https://www.cnblogs.com/songer/p/6048389.html
Copyright © 2011-2022 走看看