等效集合

等效集合

Description

要使两个集合 A,B 是等效的,我们可以先让 A 成为 B 的子集,然后再让 B 成为 A 的子集,这样就完成了。
使用上面的方法,我们要让 N 个集合都等效:每一步,你可以让集合 X 成为 Y 的子集。注意,有一些集合是已经是其他集合的子集了。求操作最少需要经过多少步?

Input

输入包含多组测试数据,每组数据的第一行是两个整数 N,M,接下来 M 行,每行两个数 X,Y,表示集合 X 已经是 Y 集合的子集。

Output

对于每组测试数据,输出一行,一个数,表示最少要经过的步数

Sample Input

4 0
3 2
1 2
1 3

Sample Output

4
2

Hint

对于 50%的数据, N <= 2000 and M <= 5000
对于 100%的数据,N <= 20000 and M <= 50000

本人是个蒟蒻,听大佬说是道Tarjan裸题,缩个点,再求出度和入度为0的点的个数,找最大值即可,说白了这道题就是求对于给定的有向图,至少要添加多少条边,才能使之成为强连通图。

有两个我错了的地方:head没清零。。。(sb才会犯的错误)、统计初度或入度为零的点(强连通分量)的个数的时候我居然是在枚举点。。。(又是sb才会犯的错误)

说完,本蒟蒻就上个代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
int dfn[20001],low[20001],mmp[20001];
int cnt=1,top=-1,number,n,m,a,b,ans,tot,t[20001];
int head[50001],to[50001],nextt[50001],sum[50001];
bool out[50001],in[50001];
void add(int x,int y)
{
    to[++tot]=y;
    nextt[tot]=head[x];
    head[x]=tot;
}
void tarjan(int u) //Tarjan缩点 
{
    dfn[u]=low[u]=cnt++;
    mmp[++top]=u;
    for(int i=head[u];i;i=nextt[i])
    {  
        int v=to[i];
        if(!dfn[v])
        {
           tarjan(v);
           if(low[v]<low[u])
              low[u]=low[v];
        }
        else if (!t[v])
        {
            if(low[u]>dfn[v])
              low[u]=dfn[v];
        }
    }
    if(dfn[u]==low[u])
    {
        number++;
        int v=0;
        do{
            v=mmp[top--];
            t[v]=number;
        }while(v!=u);
    }
}

int main()
{
     while(scanf("%d%d",&n,&m)!=EOF)
     {
          memset(head,0,sizeof(head)); 
          memset(t,0,sizeof(t));
          memset(dfn,0,sizeof(dfn));
          memset(in,0,sizeof(in));
          memset(out,0,sizeof(out));
          tot=0;
          cnt=1;
          top=0;
          number=0;
          if(m==0){
           cout<<n<<endl;
           continue;}
          for(int i=1;i<=m;i++){
            scanf("%d%d",&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=head[i];j;j=nextt[j]){
                 int v=to[j];
                 if(t[i]!=t[v]){
                   in[t[i]]++;
                   out[t[v]]++;
                 }//如果两个点(强连通分量)之间有边连接,那么在其之间加边 
             }
          }
          int indeg=0,outdeg=0;
          for(int i=1;i<=number;i++) 
          {
             if(!in[i]) indeg++;
             if(!out[i]) outdeg++;//统计初度为零或入度为零的点的个数 
          }
          int ans=max(indeg,outdeg);//求最多需要加多少条边 
          printf("%d\n",ans);
        }
        return 0;
}

本蒟蒻语文水平欠佳,所以不懂我在说什么很正常,写完这个题解我总感觉自己思路不够清晰。。。

转载请注明出处:http://www.cnblogs.com/drurry/
原文地址:https://www.cnblogs.com/drurry/p/7659017.html