分析
- 首先可以采用dfs的方式,对每个点遍历一遍,若其尚未访问,则以它为起点dfs,那么此次dfs中未遍历到的点一定不可能与遍历到的点形成强连通分量,因为强连通分量要求能够互相到达。
- 在一次dfs中,每个scc一定存在一个节点是这个scc中其他所有点的祖先节点。证明:否则,这个scc可以划分为若干棵树,这些树之间只能以横叉边相连,而横叉边只能从dfn序大的指向小的,而强连通分量要求相互到达,且其中任意两棵树一定满足其中的一棵树的所有节点的dfn序一定大于另一颗树的所有节点的dfn序,那么这就会形成矛盾。
- 我们考虑维护一个栈,保存所有能够到达当前节点x的节点。那么栈中一定包括所有x的祖先节点,还有通过反向边可以到达x的祖先节点的点。定义low[x]为x能通过最多一条非树边到达的dfn序最小的在栈中的节点。那么当回溯时若有dfn[x]==low[x]则可判定从栈顶到x的所有节点构成一个scc
- 访问到一个新节点x时,把x压进栈,dfn[x]=low[x]=++tot.
- 对于x连向的每个节点y:若y尚未访问过,则(x,y)是树枝边,先dfs(y),用lowy更新lowx(这里其实有两种情况,见Tip);若y被访问过,且y在栈中,用dfny更新lowx
- 回溯时若有若有dfn[x]==low[x],则可一直弹栈直到x出栈,弹出的所有节点构成一个scc
- 缩点时枚举每条边,若两端点分属不同scc,则加入新DAG中。
Tip:若lowy可以到达x,那么满足条件,可以更新lowx。如果lowy不可以到达x,那么lowy一定等于y,大于dfnx,一定不会对lowx造成影响。
Code(校园网络,虽然里面的记录的scc没什么用,但在别的题中也许会用到)
| #include<bits/stdc++.h> |
| using namespace std; |
| |
| inline int read() |
| { |
| register int x=0,w=1; |
| register char ch=getchar(); |
| while((ch<'0'||ch>'9')&&ch!='-') ch=getchar(); |
| if(ch=='-') {w=-1;ch=getchar();} |
| while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} |
| return x*w; |
| } |
| inline void write(int x) |
| { |
| if(x<0) putchar('-'),x=~(x-1); |
| if(x>9) write(x/10); |
| putchar('0'+x%10); |
| } |
| const int N=1e4+100; |
| int n,ins[N],stk[N],top,tot,c[N],dfn[N],low[N],cnt,ind[N],od[N]; |
| vector<int>v[N],scc[N]; |
| void tarjan(int x) |
| { |
| dfn[x]=low[x]=++tot;stk[++top]=x;ins[x]=1; |
| for(int i=0;i<v[x].size();++i) |
| { |
| int y=v[x][i]; |
| if(!dfn[y]){ |
| tarjan(y); low[x]=min(low[x],low[y]); |
| } |
| else if(ins[y]) low[x]=min(low[x],dfn[y]); |
| } |
| if(dfn[x]==low[x]){ |
| cnt++;int z; |
| do |
| { |
| z=stk[top--]; |
| scc[cnt].push_back(z);c[z]=cnt;ins[z]=0; |
| }while(z!=x); |
| } |
| } |
| signed main() |
| { |
| n=read(); |
| for(int i=1;i<=n;++i) |
| { |
| int x=read(); |
| while(x) v[i].push_back(x),x=read(); |
| } |
| for(int i=1;i<=n;++i){ |
| if(!dfn[i]) tarjan(i); |
| } |
| for(int i=1;i<=n;++i) |
| { |
| for(int j=0;j<v[i].size();++j) |
| { |
| int y=v[i][j]; |
| if(c[i]==c[y]) continue; |
| od[c[i]]++;ind[c[y]]++; |
| } |
| } |
| int a=0,b=0; |
| for(int i=1;i<=cnt;++i) { |
| if(ind[i]==0) a++; |
| if(od[i]==0) b++; |
| } |
| write(a);puts(""); |
| if(cnt==1) { |
| puts("0");return 0; |
| } |
| write(max(a,b)); |
| |
| return 0; |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】