C.可达性
题目链接
题意:
给出一个 0 ≤ N ≤ 10
5 点数、0 ≤ M ≤ 10
5 边数的有向图,
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。
ps:本题中,图中可能有环,自环,还可能是不连通图。
思路:先用tarjan算法对图中的环进行缩点,得到一个不含环的有向图。对这个图的每一条边取反,对所有点用dfs搜索出度为0的点,这些点在原图中入度为0,只有自己才能经过自己,所以这些点都是答案之一。
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 200000 + 100; const int mod = int(1e9) + 9; vector<int> e[maxn]; int ins[maxn], dfn[maxn], low[maxn], contract[maxn]; ll w[maxn]; int ind; bool ps[maxn]; stack<int> s; struct Edge { int u, v; int next; } edge[maxn]; int head[maxn]; bool vis[maxn]; int num[maxn]; int top; int n, m; int cnt; void add(int u, int v) { edge[top].u = u; edge[top].v = v; edge[top].next = head[u]; head[u] = top++; } void init() { memset(vis, 0, sizeof(vis)); memset(head, -1, sizeof(head)); top = 0; cnt = 0; } void dfs(int u) { //dfs求每一个连通图的出度为0的点 vis[u] = 1; if (head[u] == -1) { num[cnt++] = u; return; } for (int i = head[u]; i != -1; i = edge[i].next) { int v = contract[edge[i].v]; if (vis[v] == 0) { dfs(v); } } } void tarjan(int u) { //求出每一个环,将环中的所有点压缩为最小下标的那个点 if (ps[u]) return; ps[u] = u; dfn[u] = low[u] = ++ind; ins[u] = 1; s.push(u); for (int i = 0; i < e[u].size(); i++) { int v = e[u][i]; if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if (ins[v]) low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { int v; do { v = s.top(); s.pop(); ins[v] = 0; contract[v] = u; ps[v] = u; if (u != v) { w[u] += w[v]; while (!e[v].empty()) { e[u].push_back(e[v].back()); //压缩时,将所有环中的点边的关系转移到缩点的关系上 e[v].pop_back(); } } } while (u != v); } } int main() { scanf("%d %d", &n, &m); init(); memset(ps, 0, sizeof(ps)); for (int i = 1; i <= m; i++) { int u, v; scanf("%d %d", &u, &v); e[u].push_back(v); } for (int i = 1; i <= n; i++){ if (!ps[i]){ //对还不在环上的点进行判断 tarjan(i); } } for (int i = 1; i <= n; i++){ //重新建图 for (int j = 0; j<e[i].size(); j++){ int u = i; int v = contract[e[i][j]]; if (u != v) //防止缩点再与环中的点相连 add(v, u); //将所有边取反,求出出度为0的点 } } for (int i = 1; i <= n; i++){ int xx = contract[i]; //每条入边指向的节点编号k,都令其等于contract[k]. if (!vis[xx]) dfs(xx); } sort(num, num + cnt); printf("%d\n", cnt); for (int i = 0; i<cnt; i++) { printf("%d%c", num[i], i == cnt - 1 ? '\n' : ' '); } return 0; }(之前的tarjan强连通算法不会写了,我真的菜爆了.jpg╮( ̄▽ ̄"")╭)