[Tarjan] 洛谷 P2746 校园网
题目描述
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 B 在 A 学校的分发列表中, A 也不一定在 B 学校的列表中。
你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。
输入输出格式
输入格式:
输入文件的第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。
接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。
输出格式:
你的程序应该在输出文件中输出两行。
第一行应该包括一个正整数:子任务 A 的解。
第二行应该包括子任务 B 的解。
输入输出样例
题解
- 对于第一问,其实就是缩点后入读为0的点的个数
- 第二问的话,我们发现,图中只要存在入度为0的点和出度为0的点就永远不可能满足要求:“ 不论我们给哪个学校发送新软件,它都会到达其余所有的学校 ”
- 还发现,只要在入度为0的点和出度为0 的点之间连一条边,就可以同时消灭两个“不合法”的点
- 如果不能做到刚好两两配对(不妨假设入度为0的点多),就给每个多出来的入度为0的点随便找一个出度为0的点配对(也就是说一个点可以同时配多个点)
- 因此,入度为0的点数与出度为0的点数的较大值即为任务B的答案
代码
1 #include <cstdio> 2 #include <iostream> 3 #include<stack> 4 using namespace std; 5 const int N=110; 6 int n,cnt,tot,num,cnt1,cnt2,ans1,ans2,head[N],dfn[N],low[N],bel[N],r[N],c[N]; 7 bool p[N]; 8 stack<int>Q; 9 struct edge { int to,from; }e[N*N]; 10 void insert(int x,int y) { e[++cnt].to=y,e[cnt].from=head[x],head[x]=cnt; } 11 void tarjan(int x) 12 { 13 dfn[x]=low[x]=++tot,p[x]=1,Q.push(x); 14 for (int i=head[x];i;i=e[i].from) 15 if (!dfn[e[i].to]) tarjan(e[i].to),low[x]=min(low[x],low[e[i].to]); 16 else if (p[e[i].to]) low[x]=min(low[x],dfn[e[i].to]); 17 if (dfn[x]==low[x]) 18 { 19 ++num; int k; 20 do 21 { 22 k=Q.top(),Q.pop(),p[k]=0,bel[k]=num; 23 } 24 while (k!=x); 25 } 26 } 27 int main() 28 { 29 scanf("%d",&n); 30 for (int i=1,x;i<=n;i++) while(scanf("%d",&x)&&x) insert(i,x); 31 for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i); 32 for (int i=1;i<=n;i++) 33 for (int j=head[i];j;j=e[j].from) 34 if (bel[i]!=bel[e[j].to]) 35 r[bel[e[j].to]]++,c[bel[i]]++; 36 for (int i=1;i<=num;i++) 37 { 38 if (!r[i]) ans1++,cnt1++; 39 if (!c[i]) cnt2++; 40 } 41 ans2=(num==1)?0:max(cnt1,cnt2),printf("%d\n%d\n",ans1,ans2); 42 }