题目描述
给出一个 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;
}