链接
算法概述
显然,对于图中任意一个强连通分量来说,只要将软件发给其中任意一个点,就可以到达该强连通分量中的其他所有点。
基于此,我们容易想到先用Tarjan算法求强连通分量,再缩点,从而简化整张图,方便我们思考。
缩完点之后这张图就变成了一张有向无环图,第一问是求将一个软件至少直接发给多少个点,才能使得所有点都收到软件。
这个问题的答案实际上就是有向无环图中零入度点的个数(一)。
第二问是求最少需要添加几条边,才能使得整张有向无环图变成一张强连通图。
这个问题的答案实际上是有向无环图中零入度点个数与零出度点个数中的较大者(二)。
特别地,如果整张图本身就是一个强连通图,则答案为0。
整个算法过程很简单,但是难点在于想出这两个结论并且想通这两个结论。下面给出这两个结论的证明。
证明
(一)的证明:
我们设将软件提供给ans个点就能使所有点都收到,图中零入度点个数为p,零出度点个数为q,以下将零入度点称为“起点”,将零出度点称为“终点”。
即要证:ans>=p。
(1)对于起点来说,没有任何一条边指向它,即没有任何一个节点能够通过某条路径到达它,故而必须要将软件发给它。由于一共有p个起点,所以我们至少要将软件发给这p个起点,故ans>=p。
(2)证明等号成立:
考虑图中除了起点之外的点。对于其中任意一点,必然可以找到指向它的某一条边,从而找到它的某个前驱,而后又能通过这个前驱找到其前驱的前驱,以此类推。由于边数是有限的且图中无环,故这个找前驱的过程一定会持续到某个点找不到入边为止,即最终找到一个起点。那么通过从这个起点出发的这条路径便可到达我们所选的这个点。
那么,除起点外的所有点,必然都可以通过上述方法到达某个起点,我们设所能达到的所有起点数量为s。首先显然s不可能超过p,因为一共只有p个起点,故s<=p。
若s<p,则说明存在某个(或某些)起点,从它(或它们)出发无法到达图中其他任意一点,即其出度为零,故而这个(些)点就是孤立点,那么我们必须要将软件发给这个(些)点。
然后对于图中除了这个(些)点之外的点来说,我们只需要将软件发给s个点,就能到达图中其他所有节点了。故而我们直接发给的点的个数总和就是p。
若s=p,显然直接发给这p个节点即可。
故等号成立。
原命题成立。
证毕!
上述证明存在漏洞。其只证明了ans>=p且等号成立,但未证明p一定是最小值。
即若存在某个有意义的正整数t<p,是否可能ans>=t并且等号成立呢?
也就是说其并未证明ans<=p。
(一)的证明:
我们设答案为ans,图中零入度点个数为p,零出度点个数为q,以下将零入度点称为“起点”,将零出度点称为“终点”。
即要证:ans=p,等价于要证ans>=p且ans<=p。
(1)对于起点来说,没有任何一条边指向它,即没有任何一个节点能够通过某条路径到达它,故而必须要将软件发给它。由于一共有p个起点,所以我们至少要将软件发给这p个起点,故ans>=p。
(2)考虑图中除了起点之外的点。对于其中任意一点,必然可以找到指向它的某一条边,从而找到它的某个前驱,而后又能通过这个前驱找到其前驱的前驱,以此类推。由于边数是有限的且图中无环,故这个找前驱的过程一定会持续到某个点找不到入边为止,即最终找到一个起点。那么通过从这个起点出发的这条路径便可到达我们所选的这个点。
那么,除起点外的所有点,必然都可以通过上述方法到达某些起点,且所能到达的所有起点的数量不可能超过p,即ans<=p。
原命题成立。
证毕!
(二)的证明:
不妨p<=q。
(1)考虑p=1的情况。相当于从这一个起点出发,可以到达所有的终点,那么我们只需要分别从这q个终点出发向起点加一条边即可,则总共需要加q条边。
加完边之后,对于图中除起点外的任意一个点来说,我们必然可以通过不断找前驱的方式,最终到达起点,同样也可以通过不断找后继的方式,到达某个终点,而这个我们已经从这个终点向起点加了一条边,则这个终点就可以到达起点。故而这个点与起点和它所能到达的终点共同构成一个环,故这一部分是强连通的。
由于上面是拿除起点外的任意一个点来说的,故而整张图就是一张强连通图。
(2)考虑p>1的情况,则q>=p>1。
引理:当p>1时,我们必然可以找到两个起点p1,p2,使得从p1出发能够到达q1,从p2出发能够到达q2。
引理的证明:
反证:假设命题不成立,则相当于从所有起点出发,最终只能到达一个终点。则q<p,矛盾!故引理成立。
那么我们加一条从q1出发指向p2的边,则起点数变为p-1,终点数变为q-1。
只需按此方法加p-1条边,便可使起点数变为1,即p'=1,此时q'=q-(p-1)。
由(1)可知,当起点数为1时,答案即为终点数。
故总共需要加的边数ans=q'+(p-1)=q-(p-1)+(p-1)=q。
综上,答案即为q。
而p>q时同理易证。
证毕!
参考代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=110,M=10010; struct Edge{ int to,next; }edge[M]; int idx; int h[N]; int low[N],dfn[N],stk[N],ins[N],id[N]; int timenum,top,scc_cnt; int din[N],dout[N]; int n; void init(){memset(h,-1,sizeof h);} void add(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;} void tarjan(int p) { low[p]=dfn[p]=++timenum; stk[++top]=p,ins[p]=1; for(int i=h[p];~i;i=edge[i].next) { int to=edge[i].to; if(!dfn[to]) { tarjan(to); low[p]=min(low[p],low[to]); } else if(ins[to])low[p]=min(low[p],dfn[to]); } if(dfn[p]==low[p]) { scc_cnt++;int y; do{ y=stk[top--];ins[y]=0; id[y]=scc_cnt; }while(p!=y); } } int main() { init(); scanf("%d",&n); for(int i=1;i<=n;i++) { int a; while(scanf("%d",&a),a)add(i,a); } for(int i=1;i<=n;i++) if(!dfn[i])tarjan(i); for(int p=1;p<=n;p++) for(int i=h[p];~i;i=edge[i].next) { int to=edge[i].to; if(id[to]==id[p])continue; din[id[to]]++; dout[id[p]]++; } int p=0,q=0; for(int i=1;i<=scc_cnt;i++) { if(!din[i])p++; if(!dout[i])q++; } printf("%d ",p); if(scc_cnt>1)printf("%d ",max(p,q)); else puts("0"); return 0; }