加载中...

求图里面能被所有点走到的点有几个

https://www.acwing.com/problem/content/1176/

对于拓扑图 如果某一个点的出度为0 那么这个点就能被其他所有点到达
但是题目里所给的点不是拓扑图 所以我们需要缩点
强联通分量里面的所有点互相到达 外面的点到这个强联通的点可以到达
所以就可以强联通分量上的点就是我们需要的点了

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e4+10,M=5e4+10;
int n,m;
int h[N], e[M], ne[M], idx;
int dfn[N],low[N],timestamp;//low 从u开始走能遍历到的最小时间戳
int stk[N],top;        //栈代表当前未被搜完的强连通分量的所有点
//不需要建立出缩小完后的点
bool in_stk[N];//表示是不是在栈当中
int id[N],scc_cnt,Size[N];//表示每个点都是哪个强联通分量里面的 有多少个强联通分量 每个强联通分量的大小是
int dout[N];
void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void tarjan(int u){
    dfn[u]=low[u]=++timestamp;//记录时间戳
    stk[++top]=u,in_stk[u]=true;
    for (int i = h[u]; ~i ; i =ne[i] ){
        int j=e[i];
        if(!dfn[j]){//当前点没有遍历过
            tarjan(j);
            low[u]=min(low[u],low[j]);//如果j比自己还小那么说明能走到上面
        }
        //如果j在栈中 说明还没出栈 而且dfs序比当前点u小 要么这个横叉 要么指回去 两种情况j的dfs序都小 所以要更新
        //dfs序比u点大的还没有放进栈里面
        else if(in_stk[j]) low[u]=min(low[u],dfn[j]);//在站里面的
        
    }
    
    if(dfn[u]==low[u])//如果自己就是最上面的点 那么自己就是强联通分量的点
    {
        ++scc_cnt;
        int y;
        do{
            y=stk[top--];//取出栈里面的点
            in_stk[y]=false;
            id[y]=scc_cnt;
            Size[scc_cnt]++;
        }while(y!=u);//因为栈中的元素dfs序比u大 所以我们只要吧dfs序打的点pop 当y'u的时候 点u所在的所有强联通分量都标记了id
    }
}
int main()
{
    cin >> n>>m;
    memset(h, -1, sizeof h);
    while (m -- ){
        int a,b;cin>>a>>b;
        add(a, b);
    }
    //遍历所有点 如果当前点没有遍历过就遍历
    for (int i = 1; i <= n; i ++ ){
        if(!dfn[i])
            tarjan(i);
    }
    for (int i = 1; i <= n; i ++ ){
        for (int j = h[i]; ~j; j=ne[j] )//遍历所有边
        {
            int k=e[j];
            int a=id[i],b=id[k];//a b表示边的两个端点的联通编号 
            if(a!=b){//说明这个点的边还会使用到缩点之后的边
                dout[a]++;//a走到b
            }
        }
    }
    
    int zeros=0,sum=0;
    for (int i = 1; i <= scc_cnt; i ++ ){//对于缩点后的点
        if(!dout[i]){//如果出度为0
            zeros++;//出度为0的点+1
            sum+=Size[i];
            if(zeros>1){//题目结论:如果多于一个点的出度为0 那么久不回有任何一个
                sum=0;
                break;
            }
        }
    }
    printf("%d\n",sum);
    return 0;
}

posted @ 2022-08-30 20:41  liang302  阅读(34)  评论(0编辑  收藏  举报