思路:使用tarjan求强连通分量并进行缩点,判断所有入度为0的点,这个点就是必须要给予文件的点,分别计算出度,入度为零的点的个数,取二者的最大值就是把这个图变成强连通需要加的边数。
一个取值需要讨论,当这个图就是强连通图的时候,答案输出1和0.
个人经历:作为初学者这个题错了很多遍,学姐给我们讲的在某个点已经被访问过的时候low值是否更新的问题,使用的是id的判断方法,只有当id为零的时候才能更新low值,这个现在我是理解的,但当时因为错误太多看别人的代码时,看到了是否在栈中的记录方式,当时我和然然大神就方了,以为是这个题需要特别判定,但后来经过一番讨论,发现这种记录方式跟id的效果是一样的,这个证明我就不给出了,自己画一个有两个强连通分量且这两个分量有一条边相连的图就可以了,个人感觉id的效果更好,它节省了空间,但是我这里还是给出栈判别方式的代码,毕竟还是要知道这种办法是怎么回事的嘛。
#include<iostream> #include<cstdio> #include<cstring> #include<stack> using namespace std; #define maxn 111 stack<int> s; int dfn[maxn],low[maxn],id[maxn],out[maxn],in[maxn]; int tot,sum,maps[maxn][maxn],n; void tarjan(int u) { dfn[u] = low[u] = ++tot; s.push(u); for(int i = 1; i <= n; i++) { if(!maps[u][i]) continue; int vv = i; if(!dfn[vv]) { tarjan(vv); low[u] = min(low[vv],low[u]); } else if(id[vv] == 0) low[u] = min(low[u],dfn[vv]); } if(low[u] == dfn[u]) { sum++; while(!s.empty()) { int num = s.top(); s.pop(); id[num] = sum; if(num == u) break; } } return; } void init() { tot = 0,sum = 0; memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(id,0,sizeof(id)); while(!s.empty()) s.pop(); memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); memset(maps,0,sizeof(maps)); } int main() { int y; while(cin >> n) { init(); for(int i = 1; i <= n; i++) { while(cin >> y) { if(!y) break; maps[i][y] = 1; } } for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i); } for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { if(!maps[i][j]) continue; if(id[i] != id[j]) { out[id[i]]++; in[id[j]]++; } } } int ans1 = 0; for(int i = 1; i <= sum; i++) { if(!in[i]) ans1++; } if(sum == 1) ans1 = 1; int num1 = 0,num2 = 0; for(int i = 1; i <= sum; i++) { if(!in[i]) num1++; if(!out[i]) num2++; } int ans2 = max(num1,num2); if(sum == 1) ans2 = 0; cout<<ans1<<endl; cout<<ans2<<endl; } return 0; }