Time Limit: 1000MS | Memory Limit: 10000K | |
Description
Input
Output
Sample Input
5 2 4 3 0 4 5 0 0 0 1 0
Sample Output
1 2
题目大意:给定一个有向图,求: ① 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点 ② 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点
首先又得了解到一个定理:有向无环图中所有入度不为0的点,一定可以由某个入度为0的点出发可达。(由于无环,所以从任何入度不为0的点往回走,必然终止于一个入度为0的点)
则可以处理出所有的强连通分量,并将强连通分量缩成一点,新图中入度为0的点数即为第①问答案
若要新图成为一个强连通分量,则不存在出度或入度为0的点,则两者中的较大值为第②问答案
注意:如果新图只有一个强连通分量,则第②问答案为0
#include <cstdio> #include <cstring> #include <vector> using namespace std; const int MAXN=105; int n,s,e,num,cnt;//num表示时间戳,cnt表示强连通分量个数 int stak[MAXN],top;//top指向栈顶元素的后一个,也表示栈内元素个数 int dfn[MAXN],low[MAXN],color[MAXN],indeg[MAXN],outdeg[MAXN]; vector<int> edge[MAXN]; bool isIn[MAXN]; void Tarjan(int u) { dfn[u]=low[u]=++num; isIn[u]=true;//标记点u在栈中 stak[top++]=u;//当前点入栈 int v; for(int i=0;i<edge[u].size();++i) { v=edge[u][i]; if(dfn[v]==0) {//如果点v未遍历 Tarjan(v); low[u]=min(low[u],low[v]); } else if(isIn[v]&&dfn[v]<low[u]) {//如果点v已遍历且在栈中 low[u]=dfn[v]; } } if(dfn[u]==low[u]) { ++cnt;//强连通分量数+1 do { v=stak[--top]; isIn[v]=false;//标记点v不在栈中 color[v]=cnt;//强连通分量缩成一点 } while(u!=v); } } void solve() { num=cnt=top=0; memset(dfn,0,sizeof(dfn)); memset(isIn,false,sizeof(isIn)); for(int i=1;i<=n;++i) if(dfn[i]==0) Tarjan(i); memset(indeg,0,sizeof(indeg)); memset(outdeg,0,sizeof(outdeg)); for(int i=1;i<=n;++i) { for(int j=0;j<edge[i].size();++j) { if(color[i]!=color[edge[i][j]]) {//如果不在一个缩点中,则更新各自缩点的出入度 ++outdeg[color[i]]; ++indeg[color[edge[i][j]]]; } } } int cntIn=0,cntOut=0;//cntIn表示入度为0的缩点个数;cntOut表示出度为0的缩点个数 for(int i=1;i<=cnt;++i) { if(indeg[i]==0) ++cntIn; if(outdeg[i]==0) ++cntOut; } printf("%d\n%d\n",cntIn,cnt==1?0:max(cntIn,cntOut));//注意如果只有一个强连通分量(包括n==1),则不需要添加边 } int main() { while(scanf("%d",&n)==1) { for(int i=1;i<=n;++i) edge[i].clear(); for(int i=1;i<=n;++i) { scanf("%d",&e); while(e!=0) { edge[i].push_back(e); scanf("%d",&e); } } solve(); } return 0; }