https://www.luogu.org/problemnew/show/P2746
缩点以后,可以保证图里没有环,只有链。由于不存在环,每条链必然存在一个唯一的入度为0的点。显然,对于每条链,只需要将软件发给这个入度为0的点,就可以间接传给这条链上所有的点。因此,任务A就转化为了求缩点后的图中有多少个点入度为0。
下面来看任务B。我们发现,图中只要存在入度为0的点和出度为0的点就永远不可能满足要求:“ 不论我们给哪个学校发送新软件,它都会到达其余所有的学校 ”。我们还发现,只要在入度为0的点和出度为0 的点之间连一条边,就可以同时消灭两个“不合法”的点。如果不能做到刚好两两配对(不妨假设入度为0的点多),就给每个多出来的入度为0的点随便找一个出度为0的点配对(也就是说一个点可以同时配多个点)。因此,入度为0的点数与出度为0的点数的较大值即为任务B的答案。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
#include <algorithm>
using namespace std;
const int MAXN=10005;
const int MAXM=50005;
int n,p[MAXN],ec,ans1,ans2;
struct cyq{
int v,next;
}edge[MAXM];
void add(int u, int v){
edge[++ec].v = v;
edge[ec].next = p[u];
p[u] = ec;
}
int dfn[MAXN],cTime,low[MAXN];
int gid[MAXN],gc,in[MAXN],out[MAXN];
bool ins[MAXN]; stack<int> Tony;
void Tarjan(int u){
dfn[u]=low[u]=++cTime;
ins[u]=1; Tony.push(u);
for(int i=p[u];i;i=edge[i].next){
int v=edge[i].v;
if(!dfn[v])
Tarjan(v),low[u]=min(low[u],low[v]);
else if(ins[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]>=dfn[u]){
++gc;int element;
do{
element=Tony.top(); ins[element]=0;
gid[element]=gc; Tony.pop();
}while(element!=u);
}
}
int main(){
scanf("%d",&n);int y;
for(int i=1;i<=n;i++){
scanf("%d",&y);
while(y!=0)
{add(i,y);scanf("%d",&y);}
}
for(int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i);
for(int i=1;i<=n;i++){
for(int j=p[i];j;j=edge[j].next){
int v=edge[j].v;
if(gid[i]!=gid[v])
{++out[gid[i]];++in[gid[v]];}
}
}
for(int i=1;i<=gc;i++){
if(in[i]==0) ++ans1;
if(out[i]==0) ++ ans2;
}
if(gc==1){printf("1\n0\n");return 0;}
printf("%d\n%d\n",ans1,max(ans1,ans2));
return 0;
}