Wannafly挑战赛14 C 可达性(Tarjan缩点)

链接:https://www.nowcoder.com/acm/contest/81/C
来源:牛客网

题目描述

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

输入描述:

第一行为两个整数 1 ≤ n, m ≤ 105,
接下来 M 行,每行两个整数 1 ≤ u, v ≤ 105 表示从点 u 至点 v 有一条有向边。
数据保证没有重边、自环。

输出描述:

第一行输出一个整数 z,表示作为答案的点集的大小;
第二行输出 z 个整数,升序排序,表示作为答案的点集。

输入

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
解题思路

Tarjan缩点之后,所有入度为0的强连通分量中最小的标号即为所求答案.

代码实现

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e6+7;
#define INF 0x3f3f3f3f
#define IO ios::sync_with_stdio(false);\
cin.tie(0);\
cout.tie(0);
struct node
{
    int v,next;
} edge[maxn*2];
int dfn[maxn],low[maxn];
int sk[maxn];
int head[maxn],vis[maxn],cnt,tot,indexx;
int sccno[maxn],scc_num;   //标记每个点所属的强连通分量的编号、标记强连通分量的编号
int mark_sccno_num[maxn];
int mark[maxn];            //标记强连通分量入度是否为0,若不是置为0
int ans[maxn];
void init()
{
    memset(head,-1,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(sccno,-1,sizeof(sccno));
    memset(mark_sccno_num,INF,sizeof(mark_sccno_num));
    memset(vis,0,sizeof(vis));
    tot=0,cnt=0,indexx=0,scc_num=0;
}
void add(int u,int v)
{
    edge[tot].v=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++cnt;
    sk[++indexx]=u;
    vis[u]=1;
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v])
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(low[u]==dfn[u])
    {
        ++scc_num;
        int t;
        do
        {
            t=sk[indexx];
            mark_sccno_num[scc_num]=min(mark_sccno_num[scc_num],t);
            sccno[t]=scc_num;
            vis[sk[indexx]]=0;
        }
        while(u!=sk[indexx--]);
    }
}
void solve(int n)
{
    for(int i=1; i<=n; i++)
        if(!dfn[i])  tarjan(i);
    for(int i=1; i<=scc_num; i++) mark[i]=i;
    for(int u=1; u<=n; u++)
    {
        for(int i=head[u]; i!=-1; i=edge[i].next)
        {
            int v=edge[i].v;
            if(sccno[u]!=sccno[v])
            {
                mark[sccno[v]]=0;
            }
        }
    }
    int an=0;
    for(int i=1; i<=scc_num; i++)
    {
        if(mark[i])
            ans[an++]=mark_sccno_num[i];
    }
cout<<an<<endl;
        sort(ans,ans+an);
        cout<<ans[0];
        for(int i=1; i<an; i++)
            cout<<" "<<ans[i];
    cout<<endl;
}
int main()
{
    int n,m;
    init();
    cin>>n>>m;
    int x,y;
    for(int i=1; i<=m; i++)
    {
        cin>>x>>y;
        add(x,y);
    }
    solve(n);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值