tarjan
Tarjan 求 LCA
做法
总体思想:遍历每一个结点并使用并查集记录父子关系。
Tarjan 是一种 DFS 的思想。我们需要从根结点去遍历这棵树。
当遍历到某一个结点(称之为 x x x) 时,你有以下几点需要做的。
-
将当前结点标记为已经访问。
-
递归遍历所有它的子节点(称之为 y y y),并在递归执行完后用并查集合并 x x x 和 y y y。
-
遍历与当前节点有查询关系的结点(称之为 z)(即是需要查询 LCA 的另一些结点),如果 z z z 已经访问,那么 x x x 与 z z z 的 L C A LCA LCA 就是 g e t f a ( z ) getfa(z) getfa(z)(这个是并查集中的查找函数),输出或者记录下来就可以了。
这是伪代码
void tarjan(int x){
//在本代码段中,s[i]为第i个子节点 , t[i]为第i个和当前节点有查询关系的结点。
vis[x]=1;//标记已经访问,vis是记录是否已访问的数组
for (i=1;i<=子节点数;i++){//枚举子节点 (递归并合并)
tarjan(s[i]);
marge(x,s[i]);//并查集合并
}
for (i=1;i<=有查询关系的结点数;i++){
if (vis[t[i]]){
cout<<x<<"和"<<t[i]<<"的LCA是"<<getfa(t[i])<<endl;//如果t[i]已经访问了输出(getfa是并查集查找函数)
}
}
}
tarjan求强连通分量
先最初调用
1、init()
2、把图用add 存下来,注意图点标为1-n,若是[0,n-1]则给所有点++;
3、调用tarjan_init(n) 再调用suodian()
4、新图就是vectorG[]; 新图点标从1-tar
5、对于原图中的每个点u,都属于新图中的一个新点Belong[u]
新图一定是森林。
6、新图中的点u 所表示的环对应原图中的vector bcc[u]
7、旧图中u在新图中所属的点是Belong[u]
作者:九野的博客
来源:优快云
原文:https://blog.youkuaiyun.com/acmmmm/article/details/9963693
提供作者本人的模板:
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,w[100001],visit[100001],head[100001],dfn[100001],low[100001],co[100001],si[100001],st[100001],col,num,dis[100001],vis[100001],w1[100001],re[100001],v[100001],ru[100001],dai[100001],vis2[100001];
int top=0;
int e[100001],h[500001];
struct node{
int from,next,to;
}edge[500001],ed[500001];
void add(int from,int to){
edge[++top].from=from;
edge[top].to=to;
edge[top].next=head[from];
head[from]=top;
}
void tarjan(int now){
dfn[now]=++num;
low[now]=num;
st[++top]=now;
for(int i=head[now];i;i=edge[i].next){
int to=edge[i].to;
if(!dfn[to]){
tarjan(to);
low[now]=min(low[now],low[to]);
}
else if(!co[to]){
low[now]=min(low[now],dfn[to]);
}
}
if(low[now]==dfn[now]){
co[now]=++col;
dai[col]=now;
w1[col]+=w[now];
++si[col];
while(st[top]!=now){
co[st[top]]=col;
++si[col];
w1[col]+=w[st[top]];
--top;
}
--top;
}
}
int topo(){
queue<int>q;
for(int i=1;i<=col;i++){
if(ru[i]==0) q.push(i);
e[i]=w1[i];
}
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=h[now];i;i=ed[i].next){
int to=ed[i].to;
e[to]=max(e[to],e[now]+w1[to]);
ru[to]--;
if(ru[to]==0) q.push(to);
}
}
int maxn=0;
for(int i=1;i<=col;i++){
maxn=max(maxn,e[i]);
}
return maxn;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
add(u,v);
re[v]++;
}
top=0;
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=m;i++){
int from=co[edge[i].from],to=co[edge[i].to];
if(from!=to){
ed[++top].from=from;
ed[top].to=to;
ed[top].next=h[from];
h[from]=top;
ru[to]++;
}
}
cout<<topo();
return 0;
}
割点
可以使用Tarjan算法求割点(注意,还有一个求连通分量的算法也叫Tarjan算法,与此算法类似)。(Tarjan,全名Robert Tarjan,美国计算机科学家。)
首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)。
对于根节点,判断是不是割点很简单——计算其子树数量,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。
对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组dfn[]和low[],dfn[u]表示顶点u第几个被(首次)访问,low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],此时u就是割点。
但这里也出现一个问题:怎么计算low[u]。
假设当前顶点为u,则默认low[u]=dfn[u],即最早只能回溯到自身。
有一条边(u, v),如果v未访问过,继续DFS,DFS完之后,low[u]=min(low[u], low[v]);
如果v访问过(且u不是v的父亲),就不需要继续DFS了,一定有dfn[v]<dfn[u],low[u]=min(low[u], dfn[v])。
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
int read(){
int x=0,pos=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
return pos?x:-x;
}
const int N=20005,M=100005;
int head[N],top=0;
struct node{
int v,nex;
}edge[M<<1];
void add(int u,int v){
edge[++top].v=v;
edge[top].nex=head[u];
head[u]=top;
}
int n,m,cnt,dfn[N],low[N],vis[N],tot=0,ans[N],num;
void dfs(int u,int rt){
int pos=0;
dfn[u]=++cnt;
low[u]=cnt;
for(int i=head[u];i;i=edge[i].nex){
int v=edge[i].v;
if(!dfn[v]){
if(u==rt) tot++;
dfs(v,rt);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=rt&&!pos){
pos=1;
ans[++num]=u;
}
}
else low[u]=min(low[u],dfn[v]);
}
}
int cmp(int aa,int bb){
return aa<bb;
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int x=read(),y=read();
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tot=0;
dfs(i,i);
if(tot>=2){
ans[++num]=i;
}
}
}
sort(ans+1,ans+num+1,cmp);
printf("%d\n",num);
for(int i=1;i<=num;i++){
printf("%d ",ans[i]);
}
return 0;
}