#牛客网 可达性 (tarjan)

 

题目描述

给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。

输入描述:

第一行为两个整数 1 ≤ n, m ≤ 105,
接下来 M 行,每行两个整数 1 ≤ u, v ≤ 105 表示从点 u 至点 v 有一条有向边。
数据保证没有重边、自环。

输出描述:

第一行输出一个整数 z,表示作为答案的点集的大小;
第二行输出 z 个整数,升序排序,表示作为答案的点集。

示例1

输入

 

7 10
4 5
5 1
2 5
6 5
7 2
4 2
1 2
5 3
3 5
3 6

输出

 

2
4 7

题目大意 : 输入一张有向图, 输出一个点集,表示通过这些点集可以到达图中任意一点,如果有多个点集,输出字典序最小的点集

思路 : 先缩点,如果只有一个新点的话,直接输出1, 因为整张图都互相可达,1是字典序最小的。在缩点的过程中,维护两个数组,一个数组用来保存新点的点集,也就是缩点后该点原来的点有哪些,另一个数组来维护这个点集中字典序最小的,最后遍历新图,判断 入度为0的点,标记上,再依次保存到新数组中,排序,搞定!

AC代码 :

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int INF = 0x3f3f3f3f;

struct node
{
	int v, next;
}e[MAXN];
stack <int> st;
int dfn[MAXN], low[MAXN], suo[MAXN], in[MAXN], tot, scnt;
int head[MAXN], pre[MAXN], p[MAXN], n, m, cnt, C, X;
bool vis[MAXN];
void add(int from, int to) {  // 链式前向星
	e[++cnt].v = to;
	e[cnt].next = head[from];
	head[from] = cnt;
}
void tarjan(int x) {
	dfn[x] = low[x] = ++tot;
	vis[x] = 1;
	st.push(x);
	for (int i = head[x]; i != -1; i = e[i].next) {
		int v = e[i].v;
		if (!dfn[v]) {
			tarjan(v);
			low[x] = min(low[x], low[v]);
		}
		else if (vis[v]) low[x] = min(low[x], dfn[v]);
	}
	if (dfn[x] == low[x]) {
		scnt++;
		int k;
		do {
			k = st.top();
			st.pop();
			suo[k] = scnt;  // 新点
			pre[scnt] = min(k, pre[scnt]);  // 点集字典序最小的点
			vis[k] = 0;
		} while (k != x);
	}
}

int main()
{
	cin >> n >> m;
	memset(head, -1, sizeof(head));
	memset(pre, INF, sizeof(pre));
	for (int i = 0; i < m; i++) {
		int ui, vi;
		scanf("%d%d", &ui, &vi);
		add(ui, vi);
	}
	for (int i = 1; i <= n; i++) {
		if (!dfn[i]) tarjan(i);
	}
	if (scnt == 1) { cout << 1 << endl; return 0; }  // 特判
	for (int i = 1; i <= n; i++) {  // 遍历新图
		for (int j = head[i]; j != -1; j = e[j].next) {
			int u = suo[i], v = suo[e[j].v];  
			if (u != v) in[v]++;
		}
	}
	for (int i = 1; i <= scnt; i++) {
		if (!in[i]) p[C++] = pre[i];
	}
	sort(p, p + C);
	cout << C << endl;
	for (int i = 0; i < C; i++) cout << p[i] << " ";
	cout << endl;
	return 0;
}

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值