Living-Dream 系列笔记 第46期
强连通分量(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; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析