强连通分量(Strongly Connected Components
,SCC
)。
强连通:有向图中,\(x,y\) 能相互到达。
弱连通:有向图中,\(x\) 能到 \(y\),\(y\) 不能到 \(x\)(或反之)。
强连通分量:有向图 \(G\) 中一极大子图 \(G1\),使得 \(G1\) 任意两点均强连通,且 \(G1\) 不可变得更大(不能添加点)。
强连通分量要么是单点,要么是环(不一定是简单环,即有可能嵌套环)。
有向图中一定存在强连通分量。
Tarjan
算法(由 Robert Tarjan
发明):
-
\(\text{dfn}\) 表示
dfs
搜索时每个点的时间戳(即dfs
序)。 -
\(\text{low}\) 表示每个点能到达的点的最小时间戳。
-
关键点 表示首次进入环的点,其中关键点的 \(\text{dfn = low}\)。
-
\(\text{dfn}\) 在 dfs 时记录即可。
-
\(\text{low}\) 在回溯时取 \(\min\) 即可。
T1
板子。
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=1e5+5;
int n,m,cnt,tot;
int dfn[N],low[N],scc[N];
vector<int> scc_id[N];
bool instk[N];
vector<int> G[M];
stack<int> s;
void tarjan(int v){
s.push(v),instk[v]=1,dfn[v]=low[v]=++cnt;
for(int i:G[v]){
if(!dfn[i]) tarjan(i),low[v]=min(low[v],low[i]);
else if(instk[i]) low[v]=min(low[v],dfn[i]);
}
if(dfn[v]==low[v]){
++tot;
for(;s.top()!=v;s.pop())
instk[s.top()]=0,scc[s.top()]=tot,scc_id[tot].push_back(s.top());
instk[v]=0,scc[v]=tot,scc_id[tot].push_back(v),s.pop();
}
}
int main(){
cin>>n>>m;
for(int i=1,u,v;i<=m;i++)
cin>>u>>v,
G[u].push_back(v);
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
cout<<tot<<'\n';
for(int i=1;i<=n;i++){
if(!dfn[i]) continue;
sort(scc_id[scc[i]].begin(),scc_id[scc[i]].end());
for(int j:scc_id[scc[i]]) cout<<j<<' ',dfn[j]=0;
cout<<'\n';
}
return 0;
}
T2
Tarjan
缩点后 topo
进行 dp
转移即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=1e5+5;
int n,m,ans=-1e9;
int p,cnt,tot;
int a[N];
int in[N],dp[N];
int dfn[N],low[N];
int scc[N],sum[N];
bool instk[N];
struct Edge{ int u,v,w; }e[M];
vector<int> G[M];
vector<int> V[M];
stack<int> s;
void tarjan(int v){
s.push(v),instk[v]=1,dfn[v]=low[v]=++cnt;
for(int i:G[v]){
if(!dfn[i]) tarjan(i),low[v]=min(low[v],low[i]);
else if(instk[i]) low[v]=min(low[v],dfn[i]);
}
if(dfn[v]==low[v]){
++tot;
for(;s.top()!=v;s.pop()) instk[s.top()]=0,scc[s.top()]=tot,sum[tot]+=a[s.top()];
instk[v]=0,scc[v]=tot,sum[tot]+=a[v],s.pop();
}
}
void topo(){
queue<int> q;
for(int i=1;i<=tot;i++)
if(!in[i]) q.push(i),dp[i]=sum[i];
while(!q.empty()){
int now=q.front(); q.pop();
for(int i:V[now]){
--in[i],dp[i]=max(dp[i],dp[now]+sum[i]);
if(!in[i]) q.push(i);
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1,u,v;i<=m;i++) cin>>u>>v,G[u].push_back(v),e[++p]={u,v};
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=m;i++){
int x=scc[e[i].u],y=scc[e[i].v];
if(x!=y) in[y]++,V[x].push_back(y);
}
topo();
for(int i=1;i<=tot;i++) ans=max(ans,dp[i]);
cout<<ans;
return 0;
}
T3
首先还是 Tarjan
缩点,
然后新建图时记录出度,
若某个强连通分量出度为 \(0\),
则这个强连通分量的点数即为答案。
同时,若多个强连通分量出度为 \(0\),
则答案为 \(0\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+5,M=5e4+5;
int n,m,ans;
int p,q,cnt,tot;
int /*in[N],*/out[N],dp[N];
int dfn[N],low[N];
int scc[N],sz[N];
bool instk[N];
struct Edge{ int u,v; }e[M];
vector<int> G[M];
vector<int> V[M];
stack<int> s;
void tarjan(int v){
s.push(v),instk[v]=1,dfn[v]=low[v]=++cnt;
for(int i:G[v]){
if(!dfn[i]) tarjan(i),low[v]=min(low[v],low[i]);
else if(instk[i]) low[v]=min(low[v],dfn[i]);
}
if(dfn[v]==low[v]){
++tot;
for(;s.top()!=v;s.pop()) instk[s.top()]=0,scc[s.top()]=tot,sz[tot]++;
instk[v]=0,scc[v]=tot,sz[tot]++,s.pop();
}
}
/*
void topo(){
queue<int> q;
for(int i=1;i<=tot;i++)
if(!in[i]) q.push(i),dp[i]=1;
while(!q.empty()){
int now=q.front(); q.pop();
for(int i:V[now]){
--in[i],dp[i]=max(dp[i],dp[now]+1);
if(!in[i]) q.push(i);
}
}
}
*/
signed main(){
cin>>n>>m;
for(int i=1,u,v;i<=m;i++) cin>>u>>v,G[u].push_back(v),e[++p]={u,v};
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
//cout<<tot<<'\n';
//for(int i=1;i<=tot;i++) cout<<sz[i]<<'\n';
for(int i=1;i<=m;i++){
int x=scc[e[i].u],y=scc[e[i].v];
if(x!=y) V[x].push_back(y),out[x]++;
}
//topo();
for(int i=1;i<=tot;i++)
//if(dp[i]==tot) ans+=sz[i];
if(!out[i]) ans=sz[i],q++;
cout<<(q==1?ans:0);
return 0;
}