行星与王国题解(自认为很详细吧)

行星与王国

这道题目其实就是求强联通分量,上面没听懂的来看一下。好我们再次掏出一张图。

好,再来一张,不难得出下面那张图就是 dfs 生成树。然后圆圈圈出来的就是强联通分量。

再定睛一看,那一条红边就是回祖边,那么从一个点出发,一直向下遍历,然后忽得找到一个点,那个点竟然有条指回这一个点的边。说明什么,这成了一个环啦!那么,这个环上所有的点都是强联通的

但是这只是强联通啊,我们需要求的可是强连通分量啊!QWQ

那怎么办呢?我们只需要再想一下,什么时候一个点和他的所有子孙节点中的一部分构成强连通分量?他的子孙再也没有指向他的祖先的边,却有指向他自己的边因为只要他的子孙节点有指向祖先的边,显然可以构成一个更大的强联通图。

2

再举一个例子,红色是强联通分量,但是蓝色只是强联通图。那么我们只需要知道这个点 u 下面的所有子节点有没有连着这个点的祖先就行了。但是呢?我们怎么知道这个点u它下面的所有子节点一定是都与他强联通的呢?但是这好像有是不对的, $自己打草稿去。那怎么办呢?开个栈记录就行了。你不早说

如果在这个点之后被遍历到的点已经能与其下面的一部分点(也可能就只有他一个点)已经构成强连通分量,即它已经是最大的。那么把它们一起从栈里弹出来就行了。好了,那么代码怎么实现呢?我们只需要开这么几个数组

  • dfs_i ,表示这个点在dfs时的时间戳。
  • low_i ,表示这个点以及其子孙节点连的所有点中 $dfn_i$​ 最小的值
  • sta_i ,表示当前所有可能能构成是强连通分量的点。
  • vis_i ,表示一个点是不是在栈里面。

然后再说一下 tarjan 的步骤:

  1. 首先初始化 dfn_ulow_u 第几个被 dfs 到
  2. u 存入栈中,并将 vis_u 设为 true
  3. 遍历 u 的每一个能到的点,如果这个点 dfn0 ,即仍未访问过,那么就对点 v 进行 dfs ,然后 low_u = \min (low_u,low_)

于是我们得到了一个强联通分量。还有一个特别的点:tarjan 一遍是搜不完所有的点的,因为存在一些孤立点,所以我们要对一趟跑下来还没有被访问到的点继续跑 tarjan 。而怎么判断呢?只需要看看 dfs 是否为 0 就好了。所以说 tarjan 的时间复杂度就是 O(n) 。至于为什么我就不多说了,好了,上代码。

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
vector<int> g[100005];
int color[100005],dfn[200020],low[200020],sta[200020],vis[100005],cnt[100005];
int deep,top,n,m,sum,ans;
void tarjan(int u)
{
    dfn[u]=++deep;
    low[u]=deep;
    vis[u]=1;
    sta[++top]=u;
    int sz=g[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=g[u][i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else
        {
            if(vis[v])
            {
                low[u]=min(low[u],low[v]);
            }
        }
    }
    if(dfn[u]==low[u])
    {
        color[u]=++sum;
        vis[u]=0;
        while(sta[top]!=u)
        {
            color[sta[top]]=sum;
            vis[sta[top--]]=0;
        }
        top--;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int from,to;
        scanf("%d%d",&from,&to);
        g[from].push_back(to);
    }
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])
        {
            tarjan(i);
        }
    }
    for(int i=1;i<=n;i++)
    {
        cnt[color[i]]++;
    }
    for(int i=1;i<=sum;i++)
    {
        if(cnt[i]>=1)
        {
            ans++;
        }
    }
    printf("%d\n",ans);
    for(int i=1;i<=n;i++)
    {
		printf("%d ",color[i]);
	}
}

给一个爱心吧

5 个赞