题目描述
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 B 在 A 学校的分发列表中, A 也不一定在 B 学校的列表中。
你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。
输入格式
输入文件的第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。
接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。
输出格式
你的程序应该在输出文件中输出两行。
第一行应该包括一个正整数:子任务 A 的解。
第二行应该包括子任务 B 的解。
输入输出样例
输入 #1
5
2 4 3 0
4 5 0
0
0
1 0
输出 #1
1
2
说明/提示
题目翻译来自NOCOW。
USACO Training Section 5.3
题意:
分两问:
第一问:至少给几个学校输入信息,然后信息随着学校的传递关系可以到达所有学校,比如A->B,B->C,那么就可以A->C
第二问:至少将几个学校加入系统,使得给随意一个学校传入信息,所有学校都可以得到信息。
题解:第一问,非常明显直接强连通分量缩点,一个强连通分量里的所有学校,给任意一个学校传递学校其他学校都可以得到信息,那么我们就通过强连通分量缩点,把点之间建图,可以得到一棵树,那么树上节点入度为0的节点就是必须输入信息的节点,那么第一问的答案就是累计树上入度为0的节点个数,追踪到第二问,就是把树上一些点连接,让这个树变成一个新的图,而且这个图就是强连通图,那么就是累计树上入度为0节点数和出度为0的节点数,比较两者最大值,就是第二问答案,必须注意如果一开始整个图就是一个很强连通图,两问的答案都是1,需要特判。
#include<bits/stdc++.h>
using namespace std;
const int N=2e6;
typedef long long ll;
struct Node
{
int v,next;
ll w;
}edge[2*N];
//adge为初始的旧点的连接关系,edge为新节点的连接关系
int head[N],cnt;
void add_edge(int u,int v,ll w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt++;
}
bool vis[N];
//sign[i]表示i节点属于第几个最大强连通图
int DFN[N],LOW[N],sign[N];
stack<int>stk;
int ind,sign_ans,n,m;
void TarJan(int u)
{
++ind;
DFN[u]=LOW[u]=ind;
stk.push(u);
vis[u]=true;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(!DFN[v])
{
TarJan(v);
LOW[u]=min(LOW[u],LOW[v]);
}
else if(vis[v])
LOW[u]=min(LOW[u],DFN[v]);
}
if(DFN[u]==LOW[u])
{
sign_ans++;
int s;
// cout<<"第"<<sign_ans<<"个最大强连通图: ";
do{
s=stk.top();
stk.pop();
//标记旧点属于哪个新点集合
sign[s]=sign_ans;
vis[s]=false;
//打印路径
// cout<<s<<" ";
}
while(u!=s);
// cout<<endl;
}
}
//入度inq,出度tnq
int inq[N],tnq[N];
int main()
{
scanf("%d",&n);
cnt=0;
memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
int x;
scanf("%d",&x);
if(x==i)continue;
if(x==0)break;
add_edge(i,x,0);
}
}
sign_ans=0;
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!=-1;j=edge[j].next)
{
int u=sign[i];
int v=sign[edge[j].v];
//标记树上的节点入度和出度
if(u!=v)
{
tnq[u]++;
inq[v]++;
}
}
}
int ans1=0,ans2=0;
//开始累计
for(int i=1;i<=sign_ans;i++)
{
if(inq[i]==0)
ans1++;
if(tnq[i]==0)
ans2++;
}
//如果一开始就是一个强连通图
if(sign_ans==1)
cout<<1<<endl<<0<<endl;
else cout<<ans1<<endl<<max(ans1,ans2)<<endl;
return 0;
}