题目链接:https://www.luogu.org/problem/P1726
题意:求最大强联通分量,若存在两个最大的,输出字典序最小的。
思路:裸的Tarjan算法,讲之前需要介绍一些辅助数组:
dfn[ ]:表示这个点的时间戳,也就是第几个搜到的low[ ]:表示这个点能到达的最小时间戳,或者说是这个点以及其子孙节点连的所有点中dfn[ ]最小的。stack:表示当前所有可能构成强连通分量的点vis[ ]:表示一个点是否在stack中。首先从1节点进行深度优先搜索,过程中用栈维护可能构成强连通分量的点。
当遇到一个点时,有如下判断:
如果这个点没有访问过,就将这个点加入栈。如果这个点访问过,且在栈里,与这个点的low或dfn比较,更新自己的low。回溯时更新low。
当一个点遍历所有的边后这个点的low还是等于dfn,将这个点及以上出栈,这个点及栈以上的点构成一个强连通分量。
这个图不一定是一个连通图,所以跑Tarjan时要枚举每个点,若dfn[ ] == 0,则继续进行Tarjan。
由于本题要输出最大强连通分量的最小字典序,我们可以用染色法将一个联通分量的点染成一个颜色,输出时直接从小到大输出即可。
#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 7;vector <int> E[N], v[N];stack <int> sta;int n, m, x, y, op;int dfn[N], low[N], vis[N], color[N], col, cnt, res, flag;void tarjan(int now){ low[now] = dfn[now] = ++cnt; sta.push(now); vis[now] = 1; for(int i = 0; i < E[now].size(); i++) { int v = E[now][i]; if(!dfn[v]) { tarjan(v); low[now] = min(low[now], low[v]); } else if(vis[v]) low[now] = min(low[now], dfn[v]); } if(dfn[now] == low[now]) { col++; int t = 0; while(1) { int tmp = sta.top(); sta.pop(); vis[tmp]--; t++; color[tmp] = col; if(tmp == now) break; } if(t > res) { res = t; flag = col; } }}int main(){ scanf("%d%d",&n, &m); while(m--) { scanf("%d%d%d",&x, &y, &op); E[x].push_back(y); if(op == 2) E[y].push_back(x); } for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i); printf("%dn", res); for(int i = 1; i <= n; i++) { if(color[i] == flag) printf("%d ", i); }}/*5 51 2 11 3 22 4 25 1 23 5 1*/