poj1236 有线图的强连通分量 tarjan算法判断/*
/*
总结:这道题开始做的时候思路是正确的,但是我是把它当成无线图处理了
但是这道题是有向图,有向图的连通判断是tarjan算法和kosaraju算法判断几个强连通分量
无向图就是判断判断是否连通,一般有两种方法判断无向图
1;并查集
2;搜索,bfs,dfs;
这道题判断强连通分量,然后是缩点,将一个强连通分量变成一个点,那么就变成有向无环图DAG
textA的答案就是DAG中入度为零点的个数;textB的答案就是加边,就是出度为零的点加边到入度为零的边
想一想树 顶点就是入度为零的 叶子点就是出度为零 我们连边就是一棵树的叶子点连上另一颗树的顶点;这样保证了最小加边
anxb=max(n,m);
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN=20010;
const int MAXM=50010;
struct Edge{
int to,from;
}edge[MAXN];
vector<int>g[MAXN];
int head[MAXN],tot,In[MAXN],Out[MAXN];
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];
int Index,top;
int scc;//强连通分量个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量包含点的个数,数组编号1~scc
void init(int n)
{
tot=0;//
for(int i=1;i<=n;i++)
g[i].clear();
}
void addedge(int u,int v)
{
edge[tot].from=u;edge[tot].to=v;g[u].push_back(tot++);
}
void Tarjan(int u)
{
int v;
Low[u]=DFN[u]=++Index;//刚进入时两个时间点是相等的 因为都没有遍历过
Stack[top++]=u;
Instack[u]=true;
for(int i=0;i<g[u].size();i++)//链式前向星的遍历
{
v=edge[g[u][i]].to;
if(!DFN[v])//如果没有遍历过
{
Tarjan(v);
Low[u]=min(Low[u],Low[v]);// 回溯的时候改变当前节点的low值
}
else if(Instack[v])//如果遍历过且在栈中
Low[u]=min(Low[u],DFN[v]);// 更新当前节点的low值,这里的意思是两个节点之间有一条可达边
} // 而前面节点已经在栈中,那么后面的节点就可能和前面的节点在一个联通分量中
if(Low[u]==DFN[u])////发现是整个强连通分量子树里的最小根
{
scc++;//属于哪个连通分量
do{
v=Stack[--top];
Instack[v]=false;//出栈
Belong[v]=scc;//属于哪个连通分量
num[scc]++;
}while(v!=u);
}
}
void solve(int N)
{
int ansa=0,ansb=0;
memset(Out,0,sizeof(Out));
memset(In,0,sizeof(In));
memset(DFN,0,sizeof(DFN));//时间戳
memset(Instack,false,sizeof(Instack));//是否在栈中
memset(num,0,sizeof(num));//强连通分量里面的个数
Index=scc=top=0;//初始化
for(int i=1;i<=N;i++)//从1开始
if(!DFN[i])
Tarjan(i);//全部遍历一遍
for(int i=0;i<tot;i++)
if(Belong[edge[i].from]!=Belong[edge[i].to])//缩点Belong的值就是缩成的那点
{
In[Belong[edge[i].to]]++;
Out[Belong[edge[i].from]]++;
}
for(int i=1;i<=scc;i++)
{
if(In[i]==0)//入度
ansa++;
if(Out[i]==0)//出度
ansb++;
}
if(scc==1) {printf("1\n0\n");return ;}
printf("%d\n%d\n",ansa,max(ansa,ansb));
}
int main()
{
int n,m;
while(scanf("%d",&n)!=EOF)//出现Output Limit Exceeded 就是未加EOF
{
init(n);
for(int i=1;i<=n;i++)
{
while(scanf("%d",&m) && m)
addedge(i,m);
}
solve(n);
}
return 0;
}
/*
总结:这道题开始做的时候思路是正确的,但是我是把它当成无线图处理了
但是这道题是有向图,有向图的连通判断是tarjan算法和kosaraju算法判断几个强连通分量
无向图就是判断判断是否连通,一般有两种方法判断无向图
1;并查集
2;搜索,bfs,dfs;
这道题判断强连通分量,然后是缩点,将一个强连通分量变成一个点,那么就变成有向无环图DAG
textA的答案就是DAG中入度为零点的个数;textB的答案就是加边,就是出度为零的点加边到入度为零的边
想一想树 顶点就是入度为零的 叶子点就是出度为零 我们连边就是一棵树的叶子点连上另一颗树的顶点;这样保证了最小加边
anxb=max(n,m);
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN=20010;
const int MAXM=50010;
struct Edge{
int to,from;
}edge[MAXN];
vector<int>g[MAXN];
int head[MAXN],tot,In[MAXN],Out[MAXN];
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];
int Index,top;
int scc;//强连通分量个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量包含点的个数,数组编号1~scc
void init(int n)
{
tot=0;//
for(int i=1;i<=n;i++)
g[i].clear();
}
void addedge(int u,int v)
{
edge[tot].from=u;edge[tot].to=v;g[u].push_back(tot++);
}
void Tarjan(int u)
{
int v;
Low[u]=DFN[u]=++Index;//刚进入时两个时间点是相等的 因为都没有遍历过
Stack[top++]=u;
Instack[u]=true;
for(int i=0;i<g[u].size();i++)//链式前向星的遍历
{
v=edge[g[u][i]].to;
if(!DFN[v])//如果没有遍历过
{
Tarjan(v);
Low[u]=min(Low[u],Low[v]);// 回溯的时候改变当前节点的low值
}
else if(Instack[v])//如果遍历过且在栈中
Low[u]=min(Low[u],DFN[v]);// 更新当前节点的low值,这里的意思是两个节点之间有一条可达边
} // 而前面节点已经在栈中,那么后面的节点就可能和前面的节点在一个联通分量中
if(Low[u]==DFN[u])////发现是整个强连通分量子树里的最小根
{
scc++;//属于哪个连通分量
do{
v=Stack[--top];
Instack[v]=false;//出栈
Belong[v]=scc;//属于哪个连通分量
num[scc]++;
}while(v!=u);
}
}
void solve(int N)
{
int ansa=0,ansb=0;
memset(Out,0,sizeof(Out));
memset(In,0,sizeof(In));
memset(DFN,0,sizeof(DFN));//时间戳
memset(Instack,false,sizeof(Instack));//是否在栈中
memset(num,0,sizeof(num));//强连通分量里面的个数
Index=scc=top=0;//初始化
for(int i=1;i<=N;i++)//从1开始
if(!DFN[i])
Tarjan(i);//全部遍历一遍
for(int i=0;i<tot;i++)
if(Belong[edge[i].from]!=Belong[edge[i].to])//缩点Belong的值就是缩成的那点
{
In[Belong[edge[i].to]]++;
Out[Belong[edge[i].from]]++;
}
for(int i=1;i<=scc;i++)
{
if(In[i]==0)//入度
ansa++;
if(Out[i]==0)//出度
ansb++;
}
if(scc==1) {printf("1\n0\n");return ;}
printf("%d\n%d\n",ansa,max(ansa,ansb));
}
int main()
{
int n,m;
while(scanf("%d",&n)!=EOF)//出现Output Limit Exceeded 就是未加EOF
{
init(n);
for(int i=1;i<=n;i++)
{
while(scanf("%d",&m) && m)
addedge(i,m);
}
solve(n);
}
return 0;
}