二次元入口:367. 学校网络 - AcWing题库
思路:
根据题目建好有向图后走一遍tarjan算法获得强连通分量,记录每一个点的入度和出度,一个点在得到的强连通分量图里面是起点的话入度会是0,是终点的话出度会是0.
对于第一个问题,需要至少直接提供给多少个学校,就是提供给拓扑图中的所有起点的强连通分量,起点的数量就是第一问的答案。
第二问答案的结论是设拓扑图里面的起点数量为a,终点的数量为b,答案是max(a,b).
第一问的证明很好想,第二问证明?我也还没完全看懂。
证明:
假设起点的数量p,终点的数量是q(以下的连边操作都是在缩完点后的拓扑图上面进行)
特判:当强连通分量只有一个时,答案为0.
在p<=q时
1.当p==1,需要从q个终点都连一条边连向起点,这样才能使得从任意一个强连通分量出发都可以走到任意一个节点。(比较好想)
2.当p>1时,有q>=p>=1,这时候,必定会有两个起点p1,p2,和两个终点q1,q2
使得p1能够走到q1 , p2能够走到q2,从q1连一条边到p2, p和q同时减1,相当于同时减少了一个起点和一个终点。
按照这个操作进行p-1次,最后变成情形1,此时已经连的边的数量就是p-1,还需要连的边的数量就是q-(p-1),加起来就是q条
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const int N=110,M=1e4+10;
int e[M],ne[M],h[N],idx;
int dfn[N],times,low[N];
int stk[N],top;
bool in_stk[N];
int id[N],scc_cnt;
int din[N],dout[N];
int n;
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)
{
dfn[u]=low[u]=++times;
stk[++top]=u;
in_stk[u]=true;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(!dfn[j])
{
tarjan(j);
low[u]=min(low[u],low[j]);
}else if(in_stk[j])
low[u]=min(low[u],dfn[j]);
}
if(low[u]==dfn[u])
{
++scc_cnt;
int y;
do{
y=stk[top--];
in_stk[y]=false;
id[y]=scc_cnt;
}while(y!=u);
}
}
int main()
{
scanf("%d",&n);
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++)
{
int t;
while(cin>>t,t) add(i,t);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
for(int i=1;i<=n;i++)
for(int j=h[i];~j;j=ne[j])
{
int k=e[j];
int a=id[i],b=id[k];
if(a!=b)
{
dout[a]++;
din[b]++;
}
}
int a=0,b=0;
for(int i=1;i<=scc_cnt;i++)
{
if(!din[i])
{
a++;
}
if(!dout[i]) b++;
}
printf("%d\n",a);
if(scc_cnt==1) puts("0");
else printf("%d\n",max(a,b));
return 0;
}