1. 程式人生 > 實用技巧 >最近公共祖先問題(LCA)-Tarjan演算法

最近公共祖先問題(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\n",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\n",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[u]=True;
	3、FOR query(u,t) IN Querys:
			IF vis[t]==True:
				CommonAncestor[a,b] = Find(t);
END

OK