题意
有向图上有 N 个点,若干有向边。
第一问:至少给几个点传递信息,才能保证信息传遍整个图。
第二问:至少添加几条边,才能使任意选择点,都能传遍整个图。
思路
强连通分量的裸题。
强连通分量内的任意一点收到消息,内部其他各点必定都能收到消息。因此,可以把每个强连通分量缩成一个点。只需要考察入度为 0 的强连通分量的个数,就是第一问的答案。
对于第二问,是把图连接成一个强连通分量,同样可以在缩点后的图中操作。这里的做法是统计图中入度为0、出度为0的强连通分量的个数,取较大值即为第二问的答案。
原图中各ssc之间不会成环,如果成环的话就是一个ssc了。要使整个图强连通,至少要使所有分量的入度和出度都为0。这样也是必定存在合法方案的。
本题中原图只有一个强连通分量的情况需要特判。
Tarjan算法
Tarjan算法是基于bfs查找强连通分量的方法。
每个点赋予三个附加属性。时间戳,发现该点的时间;根,该节点所在强连通分量中的最小时间戳;是否在栈中标记。
从任意一点出发,对图进行bfs。
每进入一个点,标记时间戳,将根置为本身,压栈。然后搜索与它相连的其他各点。如果没有到达过,则进入那个点。如果已经到达过,并且那个点现在仍在栈中,说明这两个点在同一个强连通分量里。比较当前点的根和那个点的时间戳,如果那个点的时间戳较小,则把根改成那个点的时间戳。即这两个点并入了同一个强连通分量。
沿bfs路径返回的时候,首先更新根节点(因为出现环表示发现了强连通分量,而从下网上返回的时候靠下的点的根是真的根),并检查每个点的时间戳和根是否相等。如果相等,说明当前点就是它所在强连通分量的根。而此时的栈中,比它后进栈的元素都在这个强连通分量中。更新ssc个数,将这些点连通当前点加入一个ssc中,并全部弹栈。
题目链接
http://poj.org/problem?id=1236
AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn = 100 + 10;
int N;
int In[maxn], Out[maxn];
/***************************Tarjan算法模板***************************/
vector<int> G[maxn];
int Mark[maxn], Root[maxn], Stack[maxn]; //时间戳,根(当前分量中时间戳最小的节点),栈
bool Instack[maxn]; //是否在栈中标记
int Ssc[maxn]; //每个节点所在的强连通分量的编号
int Index, Ssc_n, Top; //搜索时用的时间戳,强连通分量总数,栈顶指针
void Tarjan(int u) //u 当前搜索到的点
{
Mark[u] = Root[u] = ++ Index; //每找到一个点,对时间戳和根初始化
Stack[Top ++] = u; //压栈
Instack[u] = true; //在栈中标记
int v;
for(int i= 0; i< G[u].size(); i++) //向下搜索
{
v = G[u][i];
if(Mark[v] == 0) //没到过的点
{
Tarjan(v); //先向下搜索
if(Root[u] > Root[v]) Root[u] = Root[v]; //更新根
}
else if(Instack[v] && Root[u] > Mark[v]) Root[u] = Mark[v]; //到过的点且点仍在栈中,试着看这个点能不能成为根
}
/*对当前点的搜索结束*/
if(Mark[u] == Root[u]) //当前点本身时根
{
Ssc_n ++; //更新强连通分量数
do{ //栈中比它后入栈的元素在以它为根的强连通分量中
v = Stack[-- Top];
Instack[v] = false;
Ssc[v] = Ssc_n;
}while(v != u); //直到它自己
}
}
void SSC()
{
memset(Mark, 0, sizeof Mark); //初始化时间戳和栈内标记
memset(Instack, false, sizeof Instack);
Index = Ssc_n = Top = 0; //初始化时间戳,强连通分量数,栈顶指针
for(int i= 1; i<= N; i++) //保证图上所有点都访问到
if(Mark[i] == 0) Tarjan(i);
}
/***************************Tarjan算法模板***************************/
int main()
{
//freopen("in.txt", "r", stdin);
scanf("%d", &N);
for(int i= 1; i<= N; i++)
{
int x;
while(scanf("%d", &x), x)
G[i].push_back(x);
}
SSC();
if(Ssc_n == 1) //只有一个强连通分量的情况
{
cout << "1\n0\n";
return 0;
}
memset(In, 0, sizeof In); //求每个强连通分量的入度和出度
memset(Out, 0, sizeof Out);
for(int u= 1; u<= N; u++)
{
for(int i= 0; i< G[u].size(); i++)
{
int v = G[u][i];
if(Ssc[u] != Ssc[v])
Out[Ssc[u]] ++, In[Ssc[v]] ++;
}
}
int S1 = 0, S2 = 0; //找入度为0、出度为0的点的数目
for(int i= 1; i<= Ssc_n; i++)
{
if(In[i] == 0) S1 ++;
if(Out[i] == 0) S2 ++;
}
cout << S1 << endl << max(S1, S2) << endl;
return 0;
}