菜鸟就要老老实实重新学起:
强连通分量
有向图所有点之间存在路线则称为强联通图,非强联通图中的强联通子图成为强连通分量。
Tarjan算法就是通过深搜,记录节点的次序及节点之后能搜索到的最小次序,当最小次序是其本身时,就是一个强连通分量的根。
参考:https://www.byvoid.com/blog/scc-tarjan/
模版:
#define N 110
vector<int>g[N]; // 邻接表表示图
int dfn[N]; // 深度优先搜索访问次序
int low[N]; // 能追溯到的最早的次序
stack<int>st; // 存储已遍历的结点
int inStack[N]; // 检查是否在栈中
int belong[N]; // 记录每个点在第几号强连通分量里
int index,cnt; // 索引号,强连通分量个数
int n,m; // 点数,边数
int in[N];
int out[N];
void init()
{
memset(dfn, -1, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(inStack, 0, sizeof(inStack));
index = cnt = 1;
}
void tarjan(int x)
{
int i, a;
// 刚搜到一个节点时low = dfn
low[x] = dfn[x] = index;
index++;
st.push(x);
// 将入栈标记设置为1
inStack[x] = 1;
int len = g[x].size();
// 遍历入栈节点的所有边
for(i=0;i<len;i++)
{
int t=g[x][i];
// 还未入过栈
if(dfn[t] == -1)
{
tarjan(t);
// 回溯的时候改变当前节点的low值
low[x] = min(low[x], low[t]);
}
// 如果新搜索到的节点已经被搜索过而且现在在栈中
else if(inStack[t])
{
// 更新当前节点的low值,这里的意思是两个节点之间有一条可达边,
// 而前面节点已经在栈中,那么后面的节点就可能和前面的节点在一个联通分量中
low[x] = min(low[x], dfn[t]);
}
}
// 最终退回来的时候 low == dfn , 没有节点能将根节点更新,那必然就是根节点
if(low[x] == dfn[x])
{
int temp;
// 一直出栈到此节点, 这些元素是一个强联通分量
while(!st.empty())
{
temp = st.top();
st.pop();
belong[temp] = cnt; // 标记强联通分量
inStack[temp] = 0;
if(temp == x)
break;
}
cnt++;
}
}
int solve()
{
int i, j;
init();
for(i = 1; i <= n; i++)
// 如果某点没被访问过,则对其进行tarjan
if(dfn[i] == -1)
// tarjan的成果是得到了一个belong数组,记录每个节点分别属于哪个强联通分量
tarjan(i);
return cnt;
}
eg:
POJ1236 Network of schools
http://poj.org/problem?id=1236
题意:
学校之间有单向通道,得到软件之后可以通过通道传给其他学校,
问1、至少给多少学校软件之后所有学校都能都得到软件。2、至少补充多少通道之后只用一个软件可以让所有学校有软件。
思路:
求最大连通分量,缩点。求入度为零和出度为零的点数。
1、就是入度为零的点数,2、入度为零和出度为零的点数的最大值。
code:
#define N 110
vector<int>g[N];
int dfn[N];
int low[N];
stack<int>st;
int inStack[N];
int belong[N];
int index,cnt;
int n,m;
int in[N];
int out[N];
void init()
{
int i, j;
int temp;
memset(dfn, -1, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(inStack, 0, sizeof(inStack));
index = cnt = 1;
for(i=1; i <= n; i++)
{
g[i].clear();
while(scanf("%d", &temp) && temp)
{
g[i].push_back(temp);
}
}
}
void tarjan(int x)
{
int i, a;
low[x] = dfn[x] = index;
index++;
st.push(x);
inStack[x] = 1;
int len = g[x].size();
for(i=0;i<len;i++)
{
int t=g[x][i];
if(dfn[t] == -1)
{
tarjan(t);
low[x] = min(low[x], low[t]);
}
else if(inStack[t])
{
low[x] = min(low[x], dfn[t]);
}
}
if(low[x] == dfn[x])
{
int temp;
while(!st.empty())
{
temp = st.top();
st.pop();
belong[temp] = cnt;
inStack[temp] = 0;
if(temp == x)
break;
}
cnt++;
}
}
int main()
{
int i, j;
int t1, t2;
while(scanf("%d", &n) != EOF)
{
init();
for(i = 1; i <= n; i++)
if(dfn[i] == -1)
tarjan(i);
for(i = 1; i <= n; i++)
{
int len=g[i].size();
for(j=0; j<len; j++)
{
int t=g[i][j];
if(belong[i] != belong[t])
{
out[belong[i]]++;
in[belong[t]]++;
}
}
}
t1 = 0, t2 = 0;
for(i = 1; i < cnt; i++)
{
if(in[i] == 0)
t1++;
if(out[i] == 0)
t2++;
}
if(cnt == 2)
printf("1\n0\n");
else
printf("%d\n%d\n", t1, max(t1, t2));
}
return 0;
}
POJ2186 popular cows
http://poj.org/problem?id=2186
题意:
有n头牛,互相之间彼此钦佩,a钦佩b,b钦佩c则视为a也钦佩c。
求被所有牛钦佩的牛的数量。
思路:
相当于有向图,
求最大连通分量,缩点。如果只有一个点出度为零则该点内包含的是答案包含的点。
如果出度为零的点不止一个则没有满足答案的牛。
code:
#define N 11000
vector<int>g[N];
int dfn[N];
int low[N];
stack<int>st;
int inStack[N];
int belong[N];
int index,cnt;
int n,m,res;
int in[N];
int out[N];
void init()
{
int i, j;
int x,y;
int temp;
memset(dfn, -1, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(inStack, 0, sizeof(inStack));
memset(out,0,sizeof(out));
memset(in,0,sizeof(in));
index = cnt = 1;
for(i=1; i <= m; i++)
{
scanf("%d%d",&x,&y);
g[x].push_back(y);
}
}
void tarjan(int x)
{
int i, a;
low[x] = dfn[x] = index;
index++;
st.push(x);
inStack[x] = 1;
int len = g[x].size();
for(i=0;i<len;i++)
{
int t=g[x][i];
if(dfn[t] == -1)
{
tarjan(t);
low[x] = min(low[x], low[t]);
}
else if(inStack[t])
{
low[x] = min(low[x], dfn[t]);
}
}
if(low[x] == dfn[x])
{
int temp;
while(!st.empty())
{
temp = st.top();
st.pop();
belong[temp] = cnt;
inStack[temp] = 0;
if(temp == x)
break;
}
cnt++;
}
}
int main()
{
int i, j;
int t1, t2;
while(scanf("%d%d", &n,&m) != EOF)
{
init();
for(i = 1; i <= n; i++)
if(dfn[i] == -1)
tarjan(i);
for(i = 1; i <= n; i++)
{
int len=g[i].size();
for(j=0; j<len; j++)
{
int t=g[i][j];
if(belong[i] != belong[t])
{
out[belong[i]]++;
in[belong[t]]++;
}
}
}
t1 = 0, t2 = 0;
for(i = 1; i < cnt; i++)
{
if(in[i] == 0)
t1++;
if(out[i] == 0)
t2++,res=i;
}
if(t2==1)
{
int sum=0;
for(i=1;i<=n;i++)
if(belong[i] == res)
sum++;
printf("%d\n",sum);
}
else
printf("0\n");
}
return 0;
}
1109

被折叠的 条评论
为什么被折叠?



