题目描述
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 B 在 A 学校的分发列表中, A 也不一定在 B 学校的列表中。

你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。

输入格式
输入文件的第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。

接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。

输出格式
你的程序应该在输出文件中输出两行。

第一行应该包括一个正整数:子任务 A 的解。

第二行应该包括子任务 B 的解。

输入输出样例
输入
5
2 4 3 0
4 5 0
0
0
1 0
输出
1
2


强连通+缩点


对于A任务,我们可以想到,对于一个强连通分量当中的点,是只用一个的,所以我们直接缩点,变成DAG,然后入度为0的个数就是答案,当然要特判只有一个强连通分量的情况。


对于B任务,仔细想想就能得到答案就是缩点后,入度为0和出度为0的个数的最大值,因为首先贪心,让出度为0的连向入度为0的,但是还有剩余,就得到答案了,但是同理特判一下即可。


AC代码:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=110,M=100010;
int n,dfn[N],low[N],cnt,in[N],out[N],col[N],co,vis[N],num[N],res;
int head[N],nex[M],to[M],tot;
stack<int> st;
inline void add(int a,int b){
	to[++tot]=b; nex[tot]=head[a]; head[a]=tot;
}
void tarjan(int x){
	dfn[x]=low[x]=++cnt;	st.push(x);	vis[x]=1;
	for(int i=head[x];i;i=nex[i]){
		if(!dfn[to[i]]){
			tarjan(to[i]);	low[x]=min(low[x],low[to[i]]);
		}else if(vis[to[i]])	low[x]=min(low[x],dfn[to[i]]);
	}
	if(low[x]==dfn[x]){
		co++;
		while(1){
			int u=st.top();	st.pop();	vis[u]=0;
			col[u]=co;	if(u==x)	break;
		}
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		int x;	while(cin>>x,x)	add(i,x);
	}
	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=nex[j]){
			if(col[to[j]]!=col[i])	out[col[i]]++,in[col[to[j]]]++;
		}
	}
	for(int i=1;i<=co;i++)	if(!in[i])	res++;	
	cout<<max(1ll,res)<<endl;
	int outnum=0,innum=0;
	for(int i=1;i<=co;i++){
		if(!in[i])	innum++;
		if(!out[i])	outnum++; 
	}
	cout<<max(innum,outnum)-(co==1)<<endl;
	return 0;
}