等效集合
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; }
本蒟蒻语文水平欠佳,所以不懂我在说什么很正常,写完这个题解我总感觉自己思路不够清晰。。。