题目描述
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 BB 在 AA 学校的分发列表中,AA 也不一定在 BB 学校的列表中。
你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。
输入格式
输入文件的第一行包括一个正整数 NN,表示网络中的学校数目。学校用前 NN 个正整数标识。
接下来 NN 行中每行都表示一个接收学校列表(分发列表),第 i+1i+1 行包括学校 ii 的接收学校的标识符。每个列表用 00 结束,空列表只用一个 00 表示。
输出格式
你的程序应该在输出文件中输出两行。
第一行应该包括一个正整数,表示子任务 A 的解。
第二行应该包括一个非负整数,表示子任务 B 的解。
题解:
Tarjan缩点,那么子任务A:必须接受的学校数目就是入度为0的点的数目,考虑子任务B:至少加边让图变成一个环,我们这里只考虑入度为0的点和出度为0的点,因为其他点都是可以互相到达的,要求最少的边数,也就是将出度为0的点与入度为0的点连边,所以答案就是两种点的数目取max即可
AC代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#include<ext/rope>
using namespace std;
using namespace __gnu_cxx;
#define LL long long
const int MAXN = 100+50;
const int MAXM = 10000+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
int dfn[MAXN],low[MAXN],head[MAXN],to[MAXM],nxt[MAXM];
int n,k,tot,cnt,top,instack[MAXN],st[MAXN],bel[MAXN];
int in[MAXN],out[MAXN];
inline void add(int u,int v){ to[++tot]=v; nxt[tot]=head[u]; head[u]=tot; }
void tarjan(int u){
dfn[u]=low[u]=++cnt; st[++top]=u; instack[u]=1;
for(int i=head[u];i;i=nxt[i]){
if(!dfn[to[i]]) tarjan(to[i]),low[u]=min(low[u],low[to[i]]);
else if(instack[to[i]]) low[u]=min(low[u],dfn[to[i]]);
}
if(dfn[u]==low[u]){
++k; int v;
do{
v=st[top--]; instack[v]=0; bel[v]=k;
}while(u!=v);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("C:\\Users\\Administrator\\Desktop\\in.txt","r",stdin);
#endif // ONLINE_JUDGE
scanf("%d",&n); int res=0,ans=0;
for(int i=1,x;i<=n;i++) while(scanf("%d",&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=nxt[j])
if(bel[i]!=bel[to[j]])
in[bel[to[j]]]++,out[bel[i]]++;
for(int i=1;i<=k;i++) if(!in[i]) res++;
for(int i=1;i<=k;i++) if(!out[i]) ans++;
if(k==1) cout<<1<<'\n'<<0<<'\n';
else cout<<res<<'\n'<<max(ans,res)<<'\n';
return 0;
}