可达性

题目描述:

给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。

u1s1,这个题目描述讲的很难懂,你说是阅读理解都不为过,特别是最后一句,什么叫这些集合生序排序后字典序最小的?我一开始理解的是“输出这些集合升序排序后字典序最小的那个集合的元素”,但听雨巨的意思是,“将每个集合升序排序后,输出每个集合的第一个元素”,艹?

思路:

说简单点就是缩点,计算缩点后每个点集的入度,输出每个入度为0的点集中最小的点的序号

那为什么要看入度为0的呢?因为入度为0的点只能通向别的点,而不能由其他点到达,所以这些入度为0的点是可以到达除他们以外的所有的点,符合要求

#include<map>
#include<set>
#include<stack>
#include<queue>
#include<cmath>
#include<cstdio>
#include<string>
#include<vector>
#include<sstream>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define eps 1.0E-8
#define endl '\n'
//#define inf 0x3f3f3f3f
#define MAX  300000 + 50
#define mod 1000000007
#define lowbit(x) (x & (-x))
#define sd(n) scanf("%d",&n)
#define sdd(n,m) scanf("%d %d",&n,&m)
#define pd(n) printf("%d\n", (n))
#define pdd(n,m) printf("%d %d\n",n, m)
#define sddd(n,m,z) scanf("%d %d %d",&n,&m,&z)
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define mem(a,b) memset((a),(b),sizeof(a))
#define max(a,b) (((a)>(b)) ? (a):(b))
#define min(a,b) (((a)>(b)) ? (b):(a))
typedef  long long ll ;
typedef unsigned long long ull;
//不开longlong见祖宗!不看范围见祖宗!
inline int IntRead(){char ch = getchar();int s = 0, w = 1;while(ch < '0' || ch > '9'){if(ch == '-') w = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0';ch = getchar();}return s * w;}

int n, m, x, y, z;

int tot;//链式前向星建图
int head[MAX];
struct ran{
    int to, nex;
}tr[MAX];
void add(int u, int v){
    tr[++tot].to = v;
    tr[tot].nex = head[u];
    head[u] = tot;
}

int tim;//时间戳
int cnt;//缩点后点的数量
int maxn;
int color[MAX];//原来的点对应的新的点的编号
int in[MAX];//缩点后每个点的编号
int num[MAX];//缩点后每个点集包含的点数
stack<int>st;
int dfn[MAX], low[MAX];
bool vis[MAX];
void tarjan(int u){
    st.push(u);
    dfn[u] = low[u] = ++tim;
    vis[u] = 1;
    for(int i = head[u]; i; i = tr[i].nex){
        int v = tr[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(vis[v])low[u] = min(low[u], dfn[v]);//这里别写错了,是判断vis[v]
    }
    if(dfn[u] == low[u]){
        int now;
        ++cnt;
        do {
            now = st.top();
            vis[now] = 0;
            st.pop();
            color[now] = cnt;
            ++num[cnt];
        } while (now != u);
        maxn = max(maxn, num[cnt]);
    }
}


int main(){
    sdd(n, m);
    for(int i = 1; i <= m; ++i){
        sdd(x, y);
        add(x, y);//建图
    }
    for(int i = 1; i <= n; ++i){
        if(!dfn[i])tarjan(i);
    }
    for(int i = 1; i <= n; ++i){//建新图
        for(int j = head[i]; j; j = tr[j].nex){
            int v = tr[j].to;
            if(color[i] != color[v]){
                ++in[color[v]];
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= cnt; ++i){
        if(!in[i])++ans;//计算有几个入度为0的点集
    }
    cout<<ans<<endl;
    mem(vis, 0);
    for(int i = 1; i <= n; ++i){
        if(!in[color[i]] && !vis[color[i]]){
            vis[color[i]] = 1;
            cout<<i<<' ';
        }
    }
    cout<<endl;
    return 0;
}