题目链接
题目大意:
给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。
数据保证没有重边、自环。
解题思路:
要保证字典序最小
那先要保证点数尽量少,有向图在强连通分量中任意点可相互到达,故一个强连通分量中最多可选
一个点,也可以不选。
考虑tarjan算法进行缩点操作,将在一个强连通分量的点标记,在不同强连通分量间建路,最后统计入度为0的点就行了。
但此题要字典序最小,在标记强连通分量时要把在同一标记下的点放在一起最后排序。
参考代码:
#include <iostream>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <algorithm>
#include <vector>
#include <string>
#include <iomanip>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <climits>
//#include <unordered_map>
#define guo312 std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define ll long long
#define Inf LONG_LONG_MAX
#define inf INT_MAX
#define endl "\n"
#define PI 3.1415926535898
using namespace std;
const int N=2e5+10;
ll n,m;
ll a[N],b[N]; // 路
ll times=1,dfn[N],low[N],flag[N]; // tarjan
vector<int> ve[N]; // 图
stack<int> p; // tarjan
ll bel[N],cnt=0,d[N]; // 标记点 强连通分量个数 记录入度
vector<int> v[N]; // 存同一标记的点
vector<int> ans; // 记录答案
void dfs(int u){ // tarjan
flag[u]=1,dfn[u]=low[u]=times++;
p.push(u);
int si=ve[u].size();
for(int i=0;i<si;i++){
if(dfn[ve[u][i]]==0){
dfs(ve[u][i]);
low[u]=min(low[u],low[ve[u][i]]);
}
else if(flag[ve[u][i]]){ //
low[u]=min(low[u],dfn[ve[u][i]]);
}
}
if(low[u]==dfn[u]){ // 是强连通分量
cnt++;
int s;
do{
s=p.top();
p.pop();
v[cnt].push_back(s);
bel[s]=cnt,flag[s]=0; // 标记
}while(s!=u);
}
}
int main(){
guo312;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i]>>b[i]; // 存路
ve[a[i]].push_back(b[i]); // 建图
}
for(int i=1;i<=n;i++){
if(dfn[i]==0){ // tarjan
dfs(i);
}
}
for(int i=1;i<=m;i++){ // 统计入度
if(bel[a[i]]!=bel[b[i]]){ // 不在同一块
d[bel[b[i]]]++; // 把入度存在块的编号里
}
}
for(int i=1;i<=cnt;i++){
if(d[i]==0){
sort(v[i].begin(),v[i].end()); // 取最小
ans.push_back(v[i][0]);
}
}
ll si=ans.size();
cout<<si<<endl;
for(int i=0;i<si;i++){
cout<<ans[i]<<" ";
}
return 0;
}
写在最后:
同一强连通分量的点可以看成一个点