南阳理工学院共有M个系,分别编号1~M,其中各个系之间达成有一定的协议,如果某系有新软件可用时,该系将允许一些其它的系复制并使用该软件。但该允许关系是单向的,即:A系允许B系使用A的软件时,B未必一定允许A使用B的软件。
现在,请你写一个程序,根据各个系之间达成的协议情况,计算出最少需要添加多少个两系之间的这种允许关系,才能使任何一个系有软件使用的时候,其它所有系也都有软件可用。
题解:看题意就是考虑如何将这个图构成一个强连通,如果图不是强连通的,那么计算图构成的树中间的入度和出度,并输出入度为0和出度为0的两者之间的最大一个。
tarjan算法思想:
用dfs遍历G中的每个顶点,通dfn[i]表示dfs时达到顶点i的时间,low[i]表示i所能直接或间接达到时间最小的顶点。(实际操作中low[i]不一定最小,但不会影响程序的最终结果)
程序开始时,order初始化为0,在dfs遍历到v时,low[v]=dfn[v]=++order,v入栈(这里的栈不是dfs的递归时系统弄出来的栈)扫描一遍v所能直接达到的顶点k,如果 k没有被访问过那么先dfs遍历k,low[v]=min(low[v],low[k]);如果k在栈里,那么low[v]=min(low[v],dfn[k])(就是这里使得low[v]不一定最小,但不会影响到这里的low[v]会小于dfn[v])。扫描完所有的k以后,如果low[v]=dfn[v]时,栈里v以及v以上的顶点全部出栈,且刚刚出栈的就是一个极大强连通分量。。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cctype>
using namespace std;
#define maxn 10005
struct node {
int u;
int next;
};
node a[maxn];
int DFN[maxn],low[maxn],Stack[maxn],instack[maxn],head[maxn];
int M,tot,ti,ret,ans;
int color[maxn];
void add(int u,int v)
{
a[tot].u=v;
a[tot].next=head[u];
head[u]=tot;
tot++;
}
void tarjan(int u)
{
DFN[u]=low[u]=++ti;
Stack[++ret]=u;
instack[u]=1;
int k;
for(int i=head[u];i;i=a[i].next)
{
k=a[i].u;
if(!DFN[k])
{
tarjan(k);
if(low[u]>low[k])
low[u]=low[k];
}
else if(instack[k]&&low[u]>DFN[k])
low[u]=DFN[k];
}
if(DFN[u]==low[u])
{
ans++;
do
{
k=Stack[ret--];
instack[k]=0;
color[k]=ans;
}while(k!=u);
}
}
int od[maxn],id[maxn];
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>M;
tot=1;
ans=ti=0;
ret=-1;
memset(head,0,sizeof(head));
memset(DFN,0,sizeof(DFN));
memset(instack,0,sizeof(instack));
memset(low,0,sizeof(low));
for(int i=1;i<=M;i++)
{
int x;
while(cin>>x,x)
{
add(i,x);
}
}
for(int i=1;i<=M;i++)
{
if(!DFN[i])
tarjan(i);
}
if(ans==1)
cout<<"0"<<endl;
else
{
memset(od,0,sizeof(od));
memset(id,0,sizeof(id));
int in,out;
in=out=0;
for(int i=1;i<=M;i++)
for(int j=head[i];j;j=a[j].next)
{
int k=a[j].u;
if(color[i]!=color[k])
{
id[color[k]]++;
od[color[i]]++;
}
}
for(int i=1;i<=ans;i++)
{
if(id[i]==0)
in++;
if(od[i]==0)
out++;
}
if(in>out)
cout<<in<<endl;
else
cout<<out<<endl;
}
}
return 0;
}