背景
题意
给定 (n) 个学校的传递关系(有向边)(以 (n) 行任意多个整数的形式给出, (0) 表示一行结束),假设给一个学校软件之后这个学校会瞬间传递到所有能传递的学校,被传递的学校也瞬间传递到所有能传递的学校,直到不能传递为止。求最少要给多少学校软件才能使所有的学校都能得到软件,另求最少要加几条关系才能使得给一个学校软件之后所有的学校都能得到软件。
解法
强连通缩点模板。
显然传递关系是一张有向图,一个强连通分量内的所有学校由于互相连通,有一个学校得到则都能得到,于是可以缩点,变成一张有向无环图( (DAG) )。那么,这张 (DAG) 上入度为 (0) 的点显然没法被其他学校传递,于是必须要给它软件。其他学校都可以从这些 (0) 入度点获得软件。于是第一问直接输出 (0) 入度点的数量即可。
考虑第二问的本质,要求的是使得一张任意给定的有向图变成强连通图的最少加边数。于是 (DAG) 上每个点的出度和入度都要变成一个整数(充要条件)。那么答案也就是 (0) 入度点的数量和 (0) 出度点的数量的较大值。当然,特殊的情况是任意给定的有向图本身就是一张强连通图,此时答案显然为 (0) ,特判的条件是图中强连通分量( (scc) )的数量为 (1) 。
代码
$View$ $Code$
//省略头文件 using namespace std; inline int read() { int ret=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { ret=(ret<<1)+(ret<<3)+ch-'0'; ch=getchar(); } return ret*f; } int n,v,cnt,stk[105],bel[105],ord,dfn[105],low[105],scccnt,ind[105],oud[105],icnt,ocnt; int num,head[10005],numc,headc[10005]; bool ins[105]; vector scc[105]; struct edge { int ver,nxt; }e[10005],c[10005]; inline void adde(int u,int v) { e[++num].ver=v; e[num].nxt=head[u]; head[u]=num; } inline void addc(int u,int v) { c[++numc].ver=v; c[numc].nxt=headc[u]; headc[u]=numc; } void tarjan(int x) { dfn[x]=++ord; low[x]=dfn[x]; stk[++cnt]=x; ins[x]=1; for(register int i=head[x];i;i=e[i].nxt) { int y=e[i].ver; if(!dfn[y]) { tarjan(y); low[x]=min(low[x],low[y]); } else if(ins[y]) low[x]=min(low[x],dfn[y]); } if(dfn[x]==low[x]) { scccnt++; int y=0; do{ y=stk[cnt--]; ins[y]=0; bel[y]=scccnt; scc[scccnt].push_back(y); }while(x!=y); } } int main() { n=read(); for(register int i=1;i<=n;i++) { while(1) { v=read(); if(!v) break; adde(i,v); } } for(register int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(register int i=1;i<=n;i++) { for(register int j=head[i];j;j=e[j].nxt) { int y=e[j].ver; if(bel[i]==bel[y]) continue; addc(bel[i],bel[y]); oud[bel[i]]++; ind[bel[y]]++; } } for(register int i=1;i<=scccnt;i++) { if(!oud[i]) ocnt++; if(!ind[i]) icnt++; } printf("%d ",icnt); if(scccnt!=1) printf("%d ",max(icnt,ocnt)); else printf("0 "); return 0; }