[关于几个tarjan算法]
首先要搞清楚的是dfn和low两个数组的含义。
dfn是时间戳,表示dfs下第一次访问的时间,然后我们tarjan搜索树上的dfs序就是dfn值。
然后low就是追溯值,表示一下节点dfn最小值:
- 子树节点;
- 通过一条不在搜索树上的边,能达到子树的节点
然后我们去考虑low[x]怎么算,根据上面的概念,我们不难推出
1.边是搜索树上的边,y是x的子节点:low[x]=min(low[x],low[y]) 2.边不是搜索树上的边:low[x]=min(low[x],dfn[y])
然后就可以去搞几个tarjan算法了
1.割边/桥
桥的定义是删掉之后G分裂成两个不相连的子图
桥的判定就是对于y是x的儿子,dfn[ x ] < low[ y ] 。然后这个的理解就是从subtree出发到不了比他更早访问的边了,然后需要注意的是因为是大于,我们还需要记一下入边,这样才能判定
- 代码大概长这样
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=4e5+5;
int hed[N],to[M],nxt[M],cnt=1;
void adde(int u,int v,int w){
cnt++;to[cnt]=v,nxt[cnt]=hed[u];
}
int num,dfn[N],low[N],bridge[N];
void tarjan(int u,int pre){
dfn[u]=low[u]=++num;
for(int i=hed[u];i;i=nxt[i]){
int v=to[i];
if(i==pre^1||i==pre)continue;
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[y] > dfn[x])
bridge[i]=bridge[i^1]=true;
}else low[u]=min(low[u],dfn[v]);
}
}
2.割点
定义是一个点和连接它的边删了之后G分裂成两个不连通子图
和桥相似,然后注意一下我们的判定是low[y]>=dfn[x]。因为是大于等于,我们就不用记pre了。
还需要注意一下的是我们对于根节点需要特判,就是如果有两个子节点(也可以当成两个节点满足low[y]>=dfn[x])才能当成割点。
- 代码长这样:
#include<bits/stdc++.h>
using namespace std;
int hed[N],to[M],nxt[M],cnt=1,dfn[N],low[N],cut[N];
inline void adde(int u,int v){
cnt++;to[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
}
int stk[N],top,root;
inline void tarjan(int u){
dfn[u]=low[u]=++num;
stk[++top]=x;
if(x==root&&hed[x]==0){
dcc[++dcnt].push_back(x);
return;
}
int flag=0;
for(int i=hed[u];i;i=nxt[i]){
int v=to[i];
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[x]){
flag++;
if(x!=root||flag>1)cut[x]=true;
}
}
}
}
3.e_DCC:边双联通分量及其缩点
一条边是边双联通图就是不存在桥的时候,然后极大边双联通图就是边双联通分量。
首先求桥是肯定的,然后我们可以像强连通分量什么的一样开个栈来搞,也可以直接dfs一遍就完了。
缩点后原图就是一棵树,然后连的边显然就是桥
- 代码(namespace真好用)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=2e6+6;
int n,m;
int dccno[N],dcc;
namespace tree{
int hed[N],to[N*2],nxt[N*2],cnt;
inline void adde(int u,int v){
++cnt;to[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
}
}
namespace graph{
int hed[N],to[M],nxt[M],cnt=1,dfn[N],low[N],num;
bool bridge[N];
inline void adde(int u,int v){
++cnt;to[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
}
inline void tarjan(int u,int pre){
low[u]=dfn[u]=++num;
for(int i=hed[u];i;i=nxt[i])if(i!=(pre^1)){
int v=to[i];
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])bridge[i]=bridge[i^1]=true;
}
else low[u]=min(low[u],dfn[v]);
}
}
inline void dfs(int x,int id){
dccno[x]=id;
for(int i=hed[x];i;i=nxt[i]){
int v=to[i];
if(dccno[v]||bridge[i])continue;
dfs(v,id);
}
}
}
int u[M],v[M];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u[i],&v[i]);
graph::adde(u[i],v[i]);
graph::adde(v[i],u[i]);
}
for(int i=1;i<=n;i++)if(!graph::dfn[i])
graph::tarjan(i,0);
for(int i=1;i<=n;i++)if(!dccno[i])
graph::dfs(i,++dcc);
for(int i=1;i<=m;i++)if(dccno[u[i]]!=dccno[v[i]]){
tree::adde(dccno[u[i]],dccno[v[i]]);
tree::adde(dccno[v[i]],dccno[u[i]]);
}
}
4.v_DCC点双连通分量
把上文的桥换成割点就是定义。
然后这个的联通分量很好求就是直接开个栈去跑,然后因为割点可能属于多个点双,我们只有开个vector去存点双。
然后缩点的话我们直接缩点是不行的,需要把每个割点单独拉出来,然后去用割点连接包含他的所有点双。然后我们的就可以将这个东西缩成一个树了。
- 代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=2e6+5;
int dcnt;
vector<int> dcc[N];
int cut[N],c[N];
int new_id[N];
namespace DCC{
int hed[N*2],to[M],nxt[M],cnt;
inline void adde(int u,int v){
cnt++;ro[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
}
inline void build(){
int num=dcnt;
for(int i=1;i<=n;i++)
if(cut[i])new_id[i]=++num;
tc=1;
for(int i=1;i<=cnt;i++){
for(int j=0;j<dcc[i].size();j++){
int x=dcc[i][j];
if(cut[x])adde(i,new_id[x]),adde(new_id[x],i);
else c[x]=i;
}
}
}
}
namespace graph{
int hed[N],to[M],nxt[M],cnt=1,dfn[N],low[N];
inline void adde(int u,int v){
cnt++;to[cnt]=v,nxt[cnt]=hed[u];hed[u]=cnt;
}
int stk[N],top,root;
inline void tarjan(int u){
dfn[u]=low[u]=++num;
stk[++top]=x;
if(x==root&&hed[x]==0){
dcc[++dcnt].push_back(x);
return;
}
int flag=0;
for(int i=hed[u];i;i=nxt[i]){
int v=to[i];
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[x]){
flag++;
if(x!=root||flag>1)cut[x]=true;
dcnt++;
int z=-1;
do{
z=stk[top--];
dcc[dcnt].push_back(z);
}while(z!=v);
dcc[dcnt].push_back(x);
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);
graph::adde(u,v);graph::adde(v,u);
}
for(int i=1;i<=n;i++)if(!dfn[i]){
top=0,root=i;
graph::tarjan(i);
}
DCC::build();
}
5.有向图强连通分量
这个不用多说了吧,注意一下有向图可以两个都用low[y]更新low[x]就好了
缩点的话就直接for边然后去重就行
- 代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=5e4+5;
int n,m;
int hed[N],nxt[M],to[M];
int cnt;
void adde(int u,int v){
cnt++;nxt[cnt]=hed[u],to[cnt]=v;hed[u]=cnt;
}
int co[N],low[N],dfn[N],si[N];
int col,num;
int st[N+M],top=0;
void Tarjan(int u){
dfn[u]=low[u]=++num;
st[++top]=u;
for(int i=hed[u];i;i=nxt[i]){
int v=to[i];
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(!co[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
co[u]=++col;
++si[col];
while(st[top]!=u){
++si[col];
co[st[top]]=col;
top--;
}
--top;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);
adde(u,v);
}
for(int i=1;i<=n;i++)
if(!dfn[i])Tarjan(i);
}