zoukankan      html  css  js  c++  java
  • 最近公共祖先问题(LCA)-Tarjan算法

    Tarjan算法的实现有很多方法,这里我们记录的是并查集维护下的Tarjan离线算法

    【离线算法】指基于在执行算法前输入数据已知的基本假设,也就是说,对于一个离线算法,在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果。

    例题-POJ.1470-Closest Common Ancestors

    首先献上一个TLE了的代码,我还不知道到底怎么会TLE了的。
    希望有个大神能帮忙看一下。如果你是新手,请跳过这段代码继续向下看。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    using namespace std;
    
    class edge{
    public:
    	int v,nxt;
    	edge(){};
    	edge(int x,int y):v(x),nxt(y){};
    };
    class query{
    public:
    	int x,y;
    	bool ok;
    	query(int a,int b):x(a),y(b),ok(false){};
    	bool operator< (const query& rhs)const{return x<rhs.x;}
    	bool check(int u){if(ok)return false;return (x==u||y==u);}
    	int mate(int u){return (x+y-u);}
    };
    const int maxn = 1000;
    int n;
    int m;
    vector<query> psd;
    int fa[maxn];
    int father[maxn];
    edge mapper[maxn];
    int head[maxn];
    int vis[maxn];
    int ans[maxn];
    int cnt;
    
    void init()
    {
    	for(int i=0;i<=n;i++) fa[i]=father[i]=i;
    	int p = n+3;
    	memset(head,-1,sizeof(head));
    	memset(ans,0,sizeof(ans));
    	memset(vis,0,sizeof(vis));
    	psd.clear();
    	cnt=0;
    }
    
    void add(int u,int v)
    {
    	mapper[cnt] = edge(u,head[v]);
    	head[v] = cnt++;
    }
    
    int readin()
    {
    	//cout<<"nump is "<<nump<<endl;
    	if(scanf("%d",&n)<=0)return 0;
    	init();
    	for(int i=0;i<n;i++)
    	{
    		int v,num;
    		scanf("%d:(%d)",&v,&num);
    		for(int k=0;k<num;k++)
    		{
    			int p;
    			scanf("%d",&p);
    			add(p,v);
    			father[p]=v;
    		}
    	}
    	scanf("%d",&m);
    	int sp = m;
    	while(sp--) while(1)
    	{	
    		char ch;int a,b;
    		ch = getchar();
    		if(ch=='(')
    		{
    			scanf(" (%d %d)",&a,&b);
    			//scanf("%d%*c%d",&a,&b);
    			psd.push_back(query(a,b));
    			getchar();break;
    		}
    	}
    	int root=1;
    	while(father[root]!=root)root=father[root];
    	return root;
    }
    
    int find(int u){return (fa[u]==u)? u : fa[u]=find(fa[u]);}
    
    int LCA(int u)
    {
    	if(vis[u])return 0;;
    	for(int i = head[u];~i;i=mapper[i].nxt)
    	{
    		int p = mapper[i].v;
    		if(!vis[p]){LCA(p);fa[p]=u;}
    	}
    	for(int k=0;k<m;k++)
    	{
    		if(psd[k].check(u) && vis[psd[k].mate(u)])
    		{
    			ans[find(psd[k].mate(u))]++;
    			psd[k].ok=true;
    		}
    	}
    	vis[u]=1;
    	return 0;
    }
    
    int solve()
    {
    	int root=readin();
    	if(!root)return 0;
    	LCA(root);
    	for(int i=1;i<=n;i++)
    	{
    		if(ans[i])printf("%d:%d
    ",i,ans[i]);
    	}
    	return 1;
    }
    
    int main()
    {
    	//freopen("in.txt","r",stdin);
    	while(solve());
    	return 0;
    }
    
    

    下面是一个AC的代码(这个代码写得还是很不错的)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    using namespace std;
    
    const int maxn = 1004;
    //采用maxn*maxn的大小
    struct node{//放置树
    	int u,v,next;
    } g[maxn*maxn];
    struct nod{//放置问题
    	int u,v,next;
    } G[maxn*maxn];
    
    int n,m;
    int head[maxn],hd[maxn];
    int tot;
    int res[maxn];
    int vis[maxn],pre[maxn],fa[maxn];
    
    void init()
    {
    	tot=0;
    	memset(vis,0,sizeof(vis));
    	memset(fa,-1,sizeof(fa));
    	memset(res,0,sizeof(res));//清空答案数组
    	memset(head,-1,sizeof(head));
    	memset(hd,-1,sizeof(hd));
    	for(int i=1;i<=n;i++) pre[i]=i;
    }
    
    void addg(int u,int v)
    {
    	g[tot].v = v;
    	g[tot].next = head[u];
    	head[u] = tot++;
    }
    
    void addG(int u,int v)
    {
    	G[tot].v = v;
    	G[tot].next = hd[u];
    	hd[u] = tot++;
    }
    
    int Find(int x)
    {
    	return (x==pre[x])? x : pre[x]=Find(pre[x]);
    }
    
    //核心函数
    void lca(int u,int fa)
    {//这里加入上一级的父亲节点fa其实是为了适配无向路
    	for(int i=head[u];~i;i=g[i].next)
    	{
    		int v=g[i].v;
    		if(v==fa)continue;//用来避免无向路带来的dfs死循环
    		if(!vis[v])
    		{
    			lca(v,u);
    			pre[v]=u;
    		}
    	}
    	vis[u]=1;//一定要在u点的子树搜索完成后才能标记
    	for(int i=hd[u];~i;i=G[i].next)
    	{//这里将询问也按照邻接表的形式进行保存
    		int v=G[i].v;
    		if(vis[v]) res[Find(v)]++;
    	}
    	//vis[u]=1 也可以写在这里
    }
    
    int main()
    {
    	while(~scanf("%d",&n))
    	{
    		init();
    		int a,b,c;
    		for(int i=0;i<n;i++)
    		{
    			scanf("%d:(%d)",&a,&b);
    			for(int j=0;j<b;j++)
    			{
    				scanf("%d",&c);
    				addg(a,c);
    				addg(c,a);
    				fa[c]=a;
    			}
    		}
    		int root=1;
    		while(fa[root]!=-1)root=fa[root];
    		scanf("%d",&m);
    		tot=0;
    		for(int i=0;i<m;i++)
    		{
    			scanf(" (%d %d)",&a,&b);
    			//这句输入中有一个空格,没有的话就会MLE
    			addG(a,b);
    			addG(b,a);
    		}
    		lca(root,root);
    		for(int i=1;i<=n;i++)
    		{
    			if(res[i]) printf("%d:%d
    ",i,res[i]);
    		}
    	}
    	return 0;
    }
    

    然后是我参考的几篇相对好一些的文章

    文章1

    文章2

    文章3


    LCA核心函数伪代码模板

    vis[s]:s是否被访问的标记-初值是False
    Father[s]:s的父亲节点-初值是s
    CommonAncestor[a,b]:指a,b两节点的最近公共祖先
    Querys:所有询问的集合
    
    /*Find是并查集的维护代码*/
    Find(x){return (x==father[x])?x:father[x]=Find(father[x]);}
    
    LCA(vertex,father):
    BEGIN
    	1、FOR u OF ALL SONs NODE OF vertex:
    			IF u == father :
    				conintue;
    			IF vis[u] == False :
    				LCA(u,vertex);
    				Father[u]=vertex;
    	2、vis[vertex]=True;
    	3、FOR query(vertex,t) IN Querys:
    			IF vis[t]==True:
    				CommonAncestor[vertex,t] = Find(t);
    END
    

    OK

  • 相关阅读:
    ubuntu 启用apache运行状态信息查看
    Loadrunner生成随机字符
    Linux 如何在 vi 里搜索关键字
    Loadrunnber 报错误:Error memory violation : Exception ACCESS_VIOLATION received.的一种情况
    Could not load file or assembly 'XXX' or one of its dependencies.
    C# 显示年月日星期
    C#中timer类的用法
    详解SQL Server的两个存储过程:sp_MSforeachtable/sp_MSforeachdb
    C# winform DataGridView 的18种常见属性
    WdatePicker日历控件使用方法
  • 原文地址:https://www.cnblogs.com/savennist/p/13445230.html
Copyright © 2011-2022 走看看