浅谈 tarjan
割点
就是记录两个数组:dfn[]
和low[]
其中dfn[]
表示访问的顺序,low[u]
用来存储 \(u\) 不经过其父亲能到达的最小时间戳。。。
搬一下 wiki 的图。。。
我们发现 \(low[v]\ge dfn[u]\) 可以表示不能回到祖先,则 \(u\) 点位割点。。。
直接上代码P3388------>
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int r;
int n,m,book[N];
int dfn[N],low[N];
vector <int> k[N];
int id=0,cnt;
void dfs(int x,int fa)
{
id++;
dfn[x]=id;
low[x]=id;
int son=0;
for(int i=0;i<k[x].size();i++)
{
int y=k[x][i];
if(!dfn[y])
{
son++;
dfs(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]&&x!=r)
{
cnt+=!book[x];
book[x]=1;
}
}
else
{
if(1)
{
low[x]=min(low[x],dfn[y]);
}
}
}
if(x==r&&son>1)
{
cnt+=!book[x];
book[x]=1;
}
return;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
k[x].push_back(y);
k[y].push_back(x);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
r=i;
dfs(i,0);
}
}
cout<<cnt<<"\n";
for(int i=1;i<=n;i++)
{
if(book[i]==1)cout<<i<<" ";
}
return 0;
}
强连通分量
如果一个有向图的节点两两互相可达,则称这个图是 强连通的
如果有一张有向图的边换成无向边可得一个连通图,则称这个图是 弱连通的
点双连通分量
步骤:
- 首先,找到到割点
- 在 dfs 中,使用栈来得到子树的每个点
- 你会发现割点一但连接了其他子树和以自己为根的子树,那就不是点双连通分量。所以,只能选择自己的父亲和以自己为根的子树。
- 特别的,要特判单独点
见代码P8435:
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=4e6+5;
int n,m;
struct edge
{
int v,nxt;
}e[M];
int head[N],et=0;
inline void add(int u,int v)
{
et++;
e[et].v=v,e[et].nxt=head[u];
head[u]=et;
}
int st[N],top=0,idx=0,dfn[N],low[N];
int cnt=0;
vector<int>k[M];
inline void dfs(int u,int fa)
{
dfn[u]=low[u]=++idx;
st[++top]=u;//栈
int son=0;
for(int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].v;
if(!dfn[v])
{
son++;
dfs(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])//发现割点
{
cnt++;
while(st[top+1]!=v)
{
k[cnt].push_back(st[top--]);
}
k[cnt].push_back(u);
}
}
else if(v!=fa)
{
low[u]=min(low[u],dfn[v]);
}
}
if(fa==0&&son==0)k[++cnt].push_back(u);
return;
}
int main()
{
memset(e,-1,sizeof e);
memset(head,-1,sizeof head);
cin>>n>>m;
for(int i=1,u,v;i<=m;i++)
{
cin>>u>>v;
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
top=0;
dfs(i,0);
}
}
cout<<cnt<<'\n';
for(int i=1;i<=cnt;i++)
{
cout<<k[i].size()<<' ';
for(int j=0;j<k[i].size();j++)
{
cout<<k[i][j]<<' ';
}
cout<<'\n';
}
return 0;
}
边双连通分量
你会发现,把所有割边删掉,剩下的就是所有边双连通分量。
这样就做完了。。。
代码P8436:
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=4e6+5;
int n,m;
struct edge
{
int v,nxt;
}e[M];
int head[N],et=1;
inline void add(int u,int v)
{
et++;
e[et].v=v,e[et].nxt=head[u];
head[u]=et;
}
int idx=0;
int cnt=0,num[N];
int dfn[N],low[N];
bool vis[M];
inline void tarjan(int u,int fa)
{
dfn[u]=low[u]=++idx;
for(int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].v;
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])
{
vis[i]=1,vis[i^1]=1;
}
}
else if(fa!=v)
{
low[u]=min(low[u],dfn[v]);
}
}
}
vector<vector<int>>k;
int book[N];
inline void dfs(int u,int num)
{
book[u]=num;
k[cnt-1].push_back(u);
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v;
if(vis[i]||book[v])continue;
dfs(v,u);
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
memset(head,-1,sizeof head);
memset(e,-1,sizeof e);
et=-1;
cin>>n>>m;
for(int i=1,u,v;i<=m;i++)
{
cin>>u>>v;
if(u==v)continue;
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);
for(int i=1;i<=n;i++)
{
if(!book[i])
{
k.push_back(vector <int> ());
dfs(i,++cnt);
}
}
cout<<cnt<<'\n';
for(int i=0;i<cnt;i++)
{
cout<<k[i].size()<<' ';
for(int j=0;j<k[i].size();j++)
{
cout<<k[i][j]<<' ';
}
cout<<'\n';
}
return 0;
}
缩点
你会发现,当dfn[u]==low[u]
时,不从自己父亲向上走,你就不能得到更小的low
值,这也就说明 \(u\) 是环上一点。。。
具体的,用一个栈来存储搜索顺序,在发现dfn[u]==low
时,将 \(u\) 的子树还在栈内的点缩成新的一个点(其实就是打标记)
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=1e5+5;
int n,m;
int p[N];
vector<int>k[N];
vector<int>e[N];
int dfn[N],low[N];
int idx=0,vis[N],st[N],top=0,id[N],sd[N],num=0;
inline void tarjan(int u,int fa)
{
low[u]=dfn[u]=++idx;
st[++top]=u;
vis[u]=1;
for(int i=0;i<k[u].size();i++)
{
int v=k[u][i];
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
{
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u])//缩点部分
{
id[u]=u;
int v;
while(v=st[top--])
{
id[v]=u;
vis[v]=0;
if(u==v)break;
p[u]+=p[v];
}
}
}
int in[N],dis[N];
inline int topsort()
{
queue<int> q;
for(int i=1;i<=n;i++)
{
if(!in[i]&&id[i]==i)
{
q.push(i);
dis[i]=p[i];
}
}
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<e[u].size();i++)
{
int v=e[u][i];
if(dis[v]<dis[u]+p[v])
{
dis[v]=dis[u]+p[v];
}
in[v]--;
if(in[v]==0)q.push(v);
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,dis[i]);
}
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>p[i];
}
for(int i=1,u,v;i<=m;i++)
{
cin>>u>>v;
if(u==v)continue;
k[u].push_back(v);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])tarjan(i,0);
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<k[i].size();j++)
{
int u=i,v=k[i][j];
if(id[u]!=id[v])
{
e[id[u]].push_back(id[v]);
in[id[v]]++;
}
}
}
cout<<topsort();
return 0;
}