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╮( ̄▽ ̄"")╭)