链接:https://www.nowcoder.com/acm/contest/81/C
来源:牛客网
题目描述
给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。
输入描述:
第一行为两个整数 1 ≤ n, m ≤ 105
,5
接下来 M 行,每行两个整数 1 ≤ u, v ≤ 10
表示从点 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
题意 : 寻找最小的点集,使得从这些点出发的可以遍历整张图
思路分析 : Trajin + 缩点
代码示例 :
const int maxn = 1e5+10; vector<int>ve[maxn]; vector<int>id[maxn]; // 强连通的编号 int n, m; int dfn[maxn], low[maxn]; // 每个点在这棵树中,最小的子树的根 int tot = 0, key = 0; int Stack[maxn], belong[maxn]; // 缩点 bool instack[maxn]; int scc; // 强连通分量的个数 void tarjin(int x){ low[x] = dfn[x] = ++key; // 注意是 ++在前,因为下面下面深搜的判断是为0表示没访问过的点,才去搜 Stack[tot++] = x; instack[x] = true; for(int i = 0; i < ve[x].size(); i++){ int to = ve[x][i]; if (!dfn[to]) { tarjin(to); low[x] = min(low[x], low[to]); } else if (instack[to]){ low[x] = min(low[x], dfn[to]); } } if (low[x] == dfn[x]){ scc++; int v; do{ v = Stack[--tot]; instack[v] = false; belong[v] = scc; id[scc].push_back(v); } while(v != x); } } int u[maxn], v[maxn], in[maxn]; vector<int>ans; void solve(){ } int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); cin >> n >> m; int a, b; memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(instack, false, sizeof(instack)); memset(in, 0, sizeof(in)); for(int i = 1; i <= m; i++){ scanf("%d%d", &u[i], &v[i]); ve[u[i]].push_back(v[i]); } for(int i = 1; i <= n; i++){ if (!dfn[i]) tarjin(i); } //for(int i = 1; i <= n; i++) printf("%d ", belong[i]); for(int i = 1; i <= m; i++){ if (belong[u[i]] == belong[v[i]]) continue; in[belong[v[i]]]++; } for(int i = 1; i <= scc; i++) sort(id[i].begin(), id[i].end()); for(int i = 1; i <= scc; i++){ if (!in[i]) { ans.push_back(id[i][0]); } } sort(ans.begin(), ans.end()); printf("%d ", ans.size()); for(int i = 0; i < ans.size(); i++){ printf("%d%c", ans[i], i==ans.size()-1?' ':' '); } return 0; }