题目描述
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 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;
}