一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 (B) 在 (A) 学校的分发列表中,(A) 也不一定在 (B) 学校的列表中。
你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。
前置知识:
分析
这道题的话,我们先考虑缩短。
不会缩点的可以看一下我的文章
既然我们缩好点了,那么整张图变成了一个 (DAG)(有向无环图)
这样就好处理了。
-
对于问题
A
我们发现既然这整张图是 (DAG),那么答案显然为入度为 (0) 的点的个数
-
对于问题
B
我们发现这整张图是 (DAG)。我们要把它变成连通图。连通图需要满足:
- 没有入度为 (0) 的点
- 没有出度为 (0) 的点
考虑入度为 (0) 和 出度为 (0) 的点两两匹配,则需要匹配 (max{ exttt{入度为} 0 exttt{的点}, exttt{入度为} 1 exttt{的点}) 次。
一些细节
注意缩点后只有一个点的情况,本身就是连通的,所以 问题 B
的答案为 (0)
#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &FF){
T RR=1;FF=0;char CH=getchar();
for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
FF*=RR;
}
template<typename T>inline void write(T x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar('0'+x%10);
}
const int MAXN=1e6+10,MAXM=1e6+10;
int s[MAXN],stop,dfn[MAXN],low[MAXN],scccnt,sccnum[MAXN],dfscnt,tot,he[MAXN],ne[MAXM<<1],ed[MAXM<<1],n,x,se,es,du[MAXN],ud[MAXN];
void add(int x,int y){
ed[++tot]=y;
ne[tot]=he[x];
he[x]=tot;
}
inline void tarjan(int now){
dfn[now]=low[now]=++dfscnt;
s[stop++]=now;
for (int i=he[now];i;i=ne[i]){
if(!dfn[ed[i]]){
tarjan(ed[i]);
low[now]=min(low[now],low[ed[i]]);
}else if(!sccnum[ed[i]]){
low[now]=min(low[now],dfn[ed[i]]);
}
}
if(dfn[now]==low[now]){
scccnt++;
do{
sccnum[s[--stop]]=scccnt;
}while(s[stop]!=now);
}
}//tarjin的板子
int main(){
read(n);
for(int i=1;i<=n;i++)
while(cin>>x&&x)add(i,x);
for(int i=1;i<=n;i++)
if(!dfn[i])tarjan(i);
for(int i=1;i<=n;i++)
for(int j=he[i];j;j=ne[j])
if(sccnum[i]!=sccnum[ed[j]]){
du[sccnum[ed[j]]]++;//统计
ud[sccnum[i]]++;//统计
}
for(int i=1;i<=scccnt;i++){
if(!du[i])se++;//入度为0的点
if(!ud[i])es++;//出度为0的点
}
cout<<se<<endl<<(scccnt==1?0:max(se,es));//小细节
return 0;
}