二次元入口: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
- #include
- #include
- #include
- 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;
- }