[USACO5.3]校园网Network of Schools
题目描述
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 B 在 A 学校的分发列表中,A 也不一定在 B 学校的列表中。
你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。
输入格式
输入文件的第一行包括一个正整数 N,表示网络中的学校数目。学校用前 N 个正整数标识。
接下来 N 行中每行都表示一个接收学校列表(分发列表),第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束,空列表只用一个 0 表示。
输出格式
你的程序应该在输出文件中输出两行。
第一行应该包括一个正整数,表示子任务 A 的解。
第二行应该包括一个非负整数,表示子任务 B 的解。
这道题就是Tarjan 缩点的比较模板的题了,但是比较重要的一点是要明白DAG图要变成一个强连通图,要加的边数是 出度为0的点数和入度为0的点数 的最大值;
代码:
#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define inf 0x3f3f3f3f
using namespace std;
const int N=20010;
const int M=1000100;
const LL mod=100000000;
int dfn[N],low[N],tot,head[N],cnt,n,sta[N],top,fa[N],in[N],out[N],sum;
vector<int>R[N];
bool vis[N],ma[N][N];
struct Node{
int to,nex;
}edge[M];
void add(int p,int q){
edge[cnt].to=q;
edge[cnt].nex=head[p];
head[p]=cnt++;
}
void Tarjan(int p){
dfn[p]=low[p]=++tot;
if(!vis[p]) vis[p]=true,sta[++top]=p;
for(int i=head[p];~i;i=edge[i].nex){
int q=edge[i].to;
if(!dfn[q]){
Tarjan(q);
low[p]=min(low[p],low[q]);
}
else if(vis[q]) low[p]=min(low[p],dfn[q]);
}
if(dfn[p]==low[p]){
sum++;//总点数
fa[p]=p;
vis[p]=false;
while(sta[top]!=p){
vis[sta[top]]=false;
fa[sta[top]]=p;
top--;
}
top--;
}
}
int main(){
memset(head,-1,sizeof(head));
cin>>n;
for(int i=1;i<=n;i++){
int q;
while(1){
scanf("%d",&q);
if(q==0) break;
add(i,q);
R[i].push_back(q);
}
}
for(int i=1;i<=n;i++){
if(!dfn[i]) Tarjan(i);
}
for(int i=1;i<=n;i++){
for(int j=0;j<R[i].size();j++){
if(fa[i]!=fa[R[i][j]]&&!ma[fa[i]][fa[R[i][j]]]){
ma[fa[i]][fa[R[i][j]]]=true;
in[fa[R[i][j]]]++;
out[fa[i]]++;
}
}
}
int ans1=0,ans2=0;
for(int i=1;i<=n;i++){
if(fa[i]==i&&in[i]==0) ans1++;
if(fa[i]==i&&out[i]==0) ans2++;
}
if(sum==1) cout<<1<<endl<<0<<endl;
else cout<<ans1<<endl<<max(ans1,ans2)<<endl;
return 0;
}