Wannafly挑战赛14 C.可达性

 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╮( ̄▽ ̄"")╭)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值