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;
}
然后是我参考的几篇相对好一些的文章
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