题目描述
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 B 在 A 学校的分发列表中, A 也不一定在 B 学校的列表中。
你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。
输入输出格式
输入格式:输入文件的第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。
接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。
输出格式:你的程序应该在输出文件中输出两行。
第一行应该包括一个正整数:子任务 A 的解。
第二行应该包括子任务 B 的解。
输入输出样例
说明
题目翻译来自NOCOW。
USACO Training Section 5.3
缩点后的DAG中,第一问是求入度为0的点的个数,第二问求最少加几条边使DAG两点之间相互可到达。
显然我们假如让所有出度为0的点能到达其他点,让所有入度为0的点能被其他点到达,就符合题意了。
所以就是入度为0的点数和出度为0的点数最大值。
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define M 100010 5 using namespace std; 6 struct point{ 7 int to,next; 8 }e[M*50]; 9 int n,num,Num,cnt,top,tot,tot0,tot1; 10 int dfn[M],low[M],st[M],co[M]; 11 int a[M],b[M],in[M],out[M],head[M]; 12 bool vis[M]; 13 void add(int from,int to) 14 { 15 e[++Num].next=head[from]; 16 e[Num].to=to; 17 head[from]=Num; 18 } 19 void tarjan(int x) 20 { 21 dfn[x]=low[x]=++cnt; 22 st[++top]=x; 23 vis[x]=true; 24 for(int i=head[x];i;i=e[i].next) 25 { 26 int to=e[i].to; 27 if(!dfn[to]) 28 { 29 tarjan(to); 30 low[x]=min(low[x],low[to]); 31 } 32 else if(vis[to]) low[x]=min(low[x],dfn[to]); 33 } 34 if(low[x]==dfn[x]) 35 { 36 tot++; 37 while(st[top+1]!=x) 38 { 39 co[st[top]]=tot; 40 vis[st[top]]=false; 41 top--; 42 } 43 } 44 } 45 int main() 46 { 47 scanf("%d",&n); 48 for(int i=1;i<=n;i++) 49 { 50 int x; scanf("%d",&x); 51 while(x!=0) 52 { 53 a[++num]=i,b[num]=x; 54 add(i,x); 55 scanf("%d",&x); 56 } 57 } 58 for(int i=1;i<=n;i++) 59 if(!dfn[i]) 60 tarjan(i); 61 for(int i=1;i<=num;i++) 62 if(co[a[i]]!=co[b[i]]) 63 { 64 in[co[b[i]]]++; 65 out[co[a[i]]]++; 66 } 67 for(int i=1;i<=tot;i++) 68 { 69 if(in[i]==0) tot0++; 70 if(out[i]==0) tot1++; 71 } 72 printf("%d\n%d",tot0,tot==1?0:max(tot0,tot1)); 73 return 0; 74 }