A-可达性_2022图论班第二章连通性例题与习题 (nowcoder.com)
题目描述公
给出一个0SN≤105点数、0≤M≤105边数的有向图,
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。
输入描述:
第一行为两个整数1 ≤n,m ≤ 105,
接下来M行,每行两个整数1≤ u, v ≤105表示从点u至点v有一条有向边。数据保证没有重边、自环。
输出描述:
第一行输出一个整数z,表示作为答案的点集的大小;第二行输出z个整数,升序排序,表示作为答案的点集。
示例1
示例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
题解:
对于一个强联通分量里是可以到达其中的任何一个点,根据题意要求字点序最小,所以我们找到下标最小的点,把这个强连通分量看作这一个点
对于什么点是到不了的呢,由于这是一个有向图
当一个点的入度为0,就没有点可以到他了,所以我们应该找到在缩点至之后,
所有入度为0的点,排序即为所求
值得注意的是,由于一个强联通分量看做一个点,如果x指向y,但是x与y属于一个强联通分量,那么y的入度不会加,因为我们已经把他们看作为一个点了
#include<iostream>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<map>
#include<cstring>
#include<cmath>
#include<stack>
#include<set>
using namespace std;
#define int long long
typedef pair<int,int> PII;
int n,m;
vector<int> p[200050];
int dfn[200050];
int low[200050];
int cnt = 0;
int vis[200050];
stack<int> s;
int color[200050];
int pre[200050];
int idx = 0;
int in[200050];
void tarjin(int u)
{
dfn[u] = low[u] = ++cnt;
vis[u] = 1;
s.push(u);
for(auto v:p[u])
{
if(!dfn[v])
{
tarjin(v);
low[u] = min(low[u],low[v]);
}
else if(vis[v])
{
low[u] = min(low[u],dfn[v]);
}
}
if(dfn[u] == low[u])
{
idx++;
while(1)
{
int v = s.top();
s.pop();
color[v] = idx;
pre[idx] = min(v,pre[idx]);
vis[v] = 0;
if(v == u)
break;
}
}
}
void solve()
{
cin >> n >> m;
memset(pre,0x3f,sizeof pre);
for(int i = 1;i <= m;i++)
{
int x,y;
cin >> x >> y;
p[x].push_back(y);
}
for(int i = 1;i <= n;i++)
{
if(!dfn[i])
{
tarjin(i);
}
}
for(int i = 1;i <= n;i++)
{
for(auto j:p[i])
{
int u = color[i];
int v = color[j];
if(u!=v)
in[v] ++;
}
}
vector<int> ans;
for(int i = 1;i <= idx;i++)
{
if(!in[i])
{
ans.push_back(pre[i]);
}
}
sort(ans.begin(),ans.end());
cout << ans.size() <<"\n";
for(auto k :ans)
{
cout <<k <<" ";
}
}
//1 2 3 4 5
signed main(){
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
int t = 1;
// cin >> t;
while(t--)
{
solve();
}
}
//5 2
//3 12