tyvj 1936 太空战队 【强连通分量】

太空战队

总时间限制:
1000ms
内存限制:
131072kB
描述

在社会经济高速发展的今天,借助高科技手段,组建太空战队的愿望就快实现了。
战队属下有N个航天员。作为空军选拔上来的佼佼者,每个航天员都有与生俱来的骄傲——他们每个人都认为自己是最强的或者是第二强的。这样,如何分组就成了司令官的难题了。司令官分组的方法是这样的:
步骤1:任意选择一个未被分组的航天员,记为当前航天员.
步骤2:把当前航天员分入一个新的组.
步骤3:如果当前航天员心目中最强的那个航天员(记为Q航天员)还没有被分组,那么把Q航天员分入当前航天员所在组,并把Q航天员作为当前航天员并重复步骤3;如果Q航天员已经被分组,那么重复步骤1,直至所有航天员都被分入了某个组。
司令官想请你帮忙计算:按上面的分组方式,最多/最少能分成几个组

分析:
         先用样例构图,是一个带“把”的环。因为每个人都很单纯,只连一个向外的边,因此这个图就可以由若干个头尾或相接或不相接的环以及构成。
         先想最小怎么办,看图,可以发现只要用手提起一条“链”的首端,整条链都可以提起来。因此它就转换成了求图中能提起一个带起一串的“链”的个数,也就是联通分量的个数
         再想最大怎么办,看图,发现2、3、4无论从谁开始都得拉出三个人。因此不能把这三个人破开。而一但我们把这个环先处理掉(即把它分好组),那么1就只好一个人孤零零一组了。因此就转化成了求图中强连通分量的个数

实现:
       实现时我们反着来,先找出图中强连通分量,并一定要记录每个人都在哪一个组,第一问就好了。接着,我们通过寻找任意两个强连通分量的联系,把在一个强连通分量中的点缩成一个点,接着我们只需寻找有多少入度为0的缩点。

参考代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <set>
#include <stack>
using namespace std;
stack<int> q;
int map[100010];
int dfn[100010],low[100010],visit[100010],p[100100]={0};
int be[100010]={0};
int n,m,ncon,a,b,indx=0;
void clear()
{
    while(!q.empty())
        q.pop();
}
void init()
{
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
    memset(visit,0,sizeof(visit));
    for(int i=1;i<=m;i++)
        map[i]=0;
    ncon=indx=0;
}
void set_tree()
{
    for(int i=1;i<=m;i++)
    {
        cin>>b;
        map[i]=b;
    }
}
void dfs(int root)
{
    visit[root]=2;
    dfn[root]=low[root]=++indx;
    q.push(root); 
    int t=map[root];
    if(visit[t]==0)
    {
        dfs(t);
        low[root]=min(low[root],low[t]);
    }
    else if(visit[t]==2)
        low[root]=min(low[root],dfn[t]);
    if(low[root]==dfn[root])//no child
    {
        ncon++;
        while(!q.empty())
        {
            int j=q.top();
            q.pop();
            visit[j]=1;
            be[j]=ncon;
            if(j==root)
                break;
        }
    }
}
void solve()
{
    for(int i=1;i<=m;i++)
        if(!visit[i])
            dfs(i);
    int ans=0;
    for(int i=1;i<=m;i++)
        if(be[i]!=be[map[i]])//缩点
            p[be[map[i]]]++;//MARK!!!!
    for(int i=1;i<=ncon;i++)
        if(p[i]==0) ans++;
    cout<<ncon<<" "<<ans;
}
int main()
{
    cin>>m;
    init();
    set_tree();
    solve();
    return 0;
}
View Code

注意“MARK!!!!”的地方,在找到两个相连但却不在同一强连通分量的点后,第一感觉是“两个点是可以互换的,标记谁都行”(标记即意味着该点所代表的强连通分量要删去,在两个相连的点中只保留一个组),但经过调试,发现不行。只能标记当前点所指向的那个点,不能标记自己,否则会多标记。因为你不能保证当前点有入度,(这里意思是:在缩点过程中,只保留那个入度为零的点作为此强联通分量的代表,因为这个点不可能再被别的点标记,如果一个强联通分量没有入度为0的点,也就是一个环,那么它要不被别的强连通分量所吞并,要么独成一体,也不可能被标记,因此不作特殊处理)如果你标记了当前点,很可能你就把一个入度为0的强连通分量标记了。

原文地址:https://www.cnblogs.com/linda-fcj/p/7206225.html