花了一晚上看了karjan,就拿这道题做一下模板题练练手吧,相关的东西都写到注释里面去了。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stack> using namespace std; int n; int cnt=0,c[110]; //cnt记录强连通分量个数,c[]记录每个点属于哪个强连通分量 int num=0,dfn[110],low[110],ins[110]; //维护tarjan所使用的信息 int din[110],dout[110]; //记录缩点后的图中各点的入度与出度 stack<int> sta; struct LIST{ int head[110],tot,to[10010],nxt[10010]; void init() { memset(head,0,sizeof(head)); memset(to,0,sizeof(to)); memset(nxt,0,sizeof(nxt)); tot=0; } void add(int u,int v) { to[++tot]=v;nxt[tot]=head[u];head[u]=tot; } }li1,li2; //两个链表,记录缩点前后的图 void tarjan(int u) { dfn[u]=low[u]=++num; //初始化该点的dfn与low值,dfn为到达u点的时间戳,low值为与u强连通的点集的最小时间戳 sta.push(u);ins[u]=1; //将点入栈,并记录其入栈状态,栈中的点是u和u的祖先 for(int i=li1.head[u];i;i=li1.nxt[i]) { int v=li1.to[i]; if(!dfn[v]) { tarjan(v); //若v还没有搜索,则先处理v low[u]=min(low[u],low[v]); //处理完v后,更新当前点的low值,因为v可能会有“后向边”,直接回到祖先点,导致low[v]更小 } else if(ins[v]) low[u]=min(low[u],dfn[v]); //若v已经搜索过,且v在栈中,可知v是u的祖先,当然直接用其dfn[v]值更新low[u] } if(dfn[u]==low[u]) //若当前点的时间戳等于其所处强连通分量的最小时间戳 { //说明从栈顶到该点的所有点都处于一个强连通分量 cnt++; //将从栈顶到该店的所有点退栈并记录信息 while(1) { int x=sta.top();ins[x]=0;sta.pop(); c[x]=cnt; //记录该点所属的强连通分量的编号,为缩点做准备 if(x==u) break; } } } int main() { li1.init(); li2.init(); scanf("%d",&n); for(int u=1,v;u<=n;u++) for(scanf("%d",&v);v!=0;scanf("%d",&v)) li1.add(u,v); for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); //开始缩点,将新图记载li2中 for(int u=1;u<=n;u++) for(int i=li1.head[u];i;i=li1.nxt[i]) { int v=li1.to[i]; if(c[u]==c[v]) continue; li2.add(c[u],c[v]); //若u和v不属于同一强连通分量,则建立这条边,顶点为c[u]和c[v] dout[c[u]]++; din[c[v]]++; //统计每个顶点的入度和出度 } //下面是这道题的一点结论 if(cnt==1) //如果这个图强连通,那么显然答案为1和0 { cout<<"1 0 "; return 0; } //统计缩点后各点的入度和出度 //最少要提供的当然是入度为0的顶点数 //加入的关系是max(din0,dout0) //因为要满足条件,就需要整个图强连通,就不能有入度或出度为0的点存在 //要消灭这些点,至少就需要max(din0,dout0)条有向边。 int ans1=0,ans2=0; for(int i=1;i<=cnt;i++) { if(din[i]==0) ans1++; if(dout[i]==0) ans2++; } cout<<ans1<<endl; cout<<max(ans1,ans2)<<endl; return 0; }