#include <bits/stdc++.h> //万能头文件 using namespace std; //名字空间,具体我也不知有啥用,但是有用到了iostream就得加这个,否则就得std:: const int maxN = 2e5 + 3; //数组大小
struct Edge { int next, to; //用结构体存邻接表 } edge[maxN]; int head[maxN], dfn[maxN], low[maxN]; int color[maxN], val[maxN], siz[maxN]; int from[maxN], to[maxN], dis[maxN]; bool vis[maxN], used[maxN]; int cnt, tot, sccnt, ans, n, m; stack<int> sta;
//以上定义了一包变量和数组
template<typename Tp> void read(Tp &x) { char c = getchar(); //先输入一个字符 x = 0; //定义x并初始化为0,用来累计输入的数 while (!isdigit(c)) c = getchar(); //如果这个字符不是数字的话,就一直读下一个字符(本题没有负数情况) do { x = x * 10 + (c ^ 48); //先把x×10,然后与c^48相加 //十进制48转换为二进制为110000,而'0'到'9'都是11****,异或会使不同的位为1,相同的位为0 c = getchar(); //读下一个字符 } while (isdigit(c)); //循环条件为读到的为数字,则一直循环到读入的不是数字为止 }
void add(int from, int to) { edge[++cnt].next = head[from], edge[cnt].to = to, head[from] = cnt; }
void tarjan(int v) { //Tarjan算法 dfn[v] = low[v] = ++tot; //标记dfn[]访问顺序,还有low[]的初始值 sta.push(v); //让点v进栈 vis[v] = true; //标记这个点被访问过 for (int i = head[v]; ~i; i = edge[i].next) { //一直循环这个点每一个出度,直到-1表示没有了,这也是为什么memset head数组时要赋-1 int u = edge[i].to; //定义u并把它赋成这条边的终点 if (!dfn[u]) { //如果u没有被访问过 tarjan(u); //找下面这个点 low[v] = min(low[v], low[u]); //这个点low[v]的值就是当前low[]的值与找到的u点的low[]值 } else if (vis[u]) //如果u被访问过了,但是还在队列中 low[v] = min(low[v], dfn[u]); //low[v]就取这个点的low值与循环到的点u的dfn[u]的最小值 } if (dfn[v] == low[v]) { //如果发现v这个点的dfn[]和low[]相等,说明这个点是一个强连通分量的“根”。 sccnt++; //scc(Strongly Connected Component), cnt(count),就是强连通分量的个数 int u; //定义u变量,作为栈顶元素 do { u = sta.top(); //将u赋值为sta栈的栈顶元素 vis[u] = false; //将u弹出 sta.pop(); //同上 color[u] = sccnt; //将u标记为这个强连通分量里的点 siz[sccnt] += val[u]; //这个强连通分量的权值加上u这个点的权值 } while (v != u); //当v == u之后,结束循环 } } void spfa(int s) { //LPFA开始 queue<int> que; //定义队列que que.push(s); //将s push进队列 dis[s] = siz[s]; //将s出发的最长路的值初始化为它所在连通块的权值之和 used[s] = true; //标记s在队列中 while (!que.empty()) { //当队列不为空(即有值)时循环 int u = que.front(); //把u赋为que的队首元素 que.pop(); //删除que中的第一个元素 used[u] = false; //标记u不在队列中 for (int i = head[u]; ~i; i = edge[i].next) { //循环从u出发的所有边 int v = edge[i].to; //定义点v并把它赋值为这条边的终点 if (dis[v] < dis[u] + siz[v]) { //如果现在的“最长路”没有先走u的长度长 dis[v] = dis[u] + siz[v]; //就对路径进行“扩张”操作 if (!used[v]) { //如果点v不在队列中 used[v] = true; //标记它加入队列 que.push(v); //把它加入队列 } } } } for (int i = 1; i <= sccnt; i++) //循环每一个强连通分量,找dis(最长路)的最大值 ans = max(ans, dis[i]); //如果dis[i]比答案大,就让答案为dis[i] } int main() { memset(head, -1, sizeof(head)); //把head[]数组初始化为-1,具体原因见tarjan函数 read(n), read(m); //输入n、m for (int i = 1; i <= n; i++) read(val[i]); //输入每一个点的权值 for (int i = 1; i <= m; i++) { read(from[i]), read(to[i]); //输入每一条边的起点和重点 add(from[i], to[i]); //把起点和终点连一条边 } for (int i = 1; i <= n; i++) //循环每一个点 if (!dfn[i]) tarjan(i); //如果dfn[i]没有值,即这个点被没有访问过,需要访问; //如果dfn[i]已经有一个值,说明这个点被访问过了,不用担心漏了, //同时也为了节省时间,就不访问了。 memset(head, -1, sizeof(head)); //将原来的head[]数组清空,以便重新建图 memset(edge, 0, sizeof(edge)); //将原来的edge[]数组清空,以便重新建图 for (int i = 1; i <= m; i++) //循环每一条边 if (color[from[i]] != color[to[i]]) //如果这条边的出发点和终止点不在同一个强连通分量中 add(color[from[i]], color[to[i]]); //就连一条边 for (int i = 1; i <= sccnt; i++) spfa(i); //循环每一个连通块(单独的一个点也是一个连通块),因为每一个连通块已经缩成一个点,所以就可以当作一个点来对待 printf("%d\n", ans); //输出答案 }
/* * @Author FrankWKD (wangkedi01) * @Date 2025-06-21 * @Source "--" * @License GNU General Public License 2.0 * @FileName Template.cpp * @FilePath /media/frank/FrankW/_default/_Mine!/Working/code-spaces/czos/Tarjan_SCC/Template.cpp * @Solution Tarjan求SCC模板,同T1 */
// #pragma GCC optimize(3) #include<bits/stdc++.h> usingnamespace std; constint N = 1e4 + 10, M = 5e4 + 10; structnode { int to, nxt; } a[M];
int pre[N], k = 0; int dfn[N], low[N], times = 0; stack<int> st; bool inst[N]; int sccnt = 0; int si[N]; // 每个SCC内部的节点数 int id[N]; // 每个点属于哪个SCC int n, m, x, y; voidtarjan(int x){ // 以x为起点进行SCC的查找(只负责这一层) dfn[x] = low[x] = ++times; st.push(x); inst[x] = true; for (int i = pre[x]; i; i = a[i].nxt) { int to = a[i].to; if (!dfn[to]) { tarjan(to); // 逐层向下递归相邻节点 low[x] = min(low[x], low[to]);
} elseif (inst[to]) low[x] = min(low[x], low[to]); // 找到了闭环!更新SCC! } if (low[x] == dfn[x]) { sccnt++; // 找到SCC入口,SCC数量++ 开始弹栈 int t; do { t = st.top(); st.pop(); inst[t] = false; si[sccnt]++; id[t] = sccnt; } while (t != x); } } voidadd(int u, int v){ a[++k] = {v, pre[u]}; pre[u] = k; } intmain(){ // ios::sync_with_stdio(false); // cin.tie(0); cout.tie(0); cin >> n >> m; for (int i = 1; i <= m; i++) { cin >> x >> y; add(x, y); } for (int i = 1; i <= n; i++) { if (!dfn[i]) { tarjan(i); } } int ans = 0; for (int i = 1; i <= sccnt; i++) { if (si[i] > 1) ans++; // 因为题目要求大小大于1的SCC的数量,所以这里我们再次统计一下 } cout << ans; return0; }