kuangbin老师讲的非常好。。直接拷他的思路吧。
强连通分量缩点求入度为0的个数和出度为0的分量个数
题目大意:N(2
也就是:
— 给定一个有向图,求:
1) 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点
2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点
— 顶点数<= 100
解题思路:
— 1. 求出所有强连通分量
— 2. 每个强连通分量缩成一点,则形成一个有向无环图DAG。
— 3. DAG上面有多少个入度为0的顶点,问题1的答案就是多少
在DAG上要加几条边,才能使得DAG变成强连通的,问题2的答案就是多少
加边的方法:
要为每个入度为0的点添加入边,为每个出度为0的点添加出边
假定有 n 个入度为0的点,m个出度为0的点,如何加边?
把所有入度为0的点编号 0,1,2,3,4 ....N -1
每次为一个编号为i的入度0点可达的出度0点,添加一条出边,连到编号为(i+1)%N 的那个出度0点,
这需要加n条边
若 m <= n,则
加了这n条边后,已经没有入度0点,则问题解决,一共加了n条边
若 m > n,则还有m-n个入度0点,则从这些点以外任取一点,和这些点都连上边,即可,这还需加m-n条边。
所以,max(m,n)就是第二个问题的解
此外:当只有一个强连通分支的时候,就是缩点后只有一个点,虽然入度出度为0的都有一个,但是实际上不需要增加清单的项了,所以答案是1,0;
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e3+5;
int n, low[maxn], dfn[maxn], belong[maxn], dfs_clock, scc_cnt;
int in[maxn], out[maxn];
vector g[maxn];
stack s;
void tarjan(int u)
{
dfn[u] = low[u] = ++dfs_clock;
s.push(u);
for(int i = 0; i < g[u].size(); i++)
{
int v = g[u][i];
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(!belong[v])
low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
scc_cnt++;
while(1)
{
int x = s.top(); s.pop();
belong[x] = scc_cnt;
if(x == u) break;
}
}
}
void find_scc()
{
while(!s.empty()) s.pop();
dfs_clock = scc_cnt = 0;
memset(belong, 0, sizeof(belong));
memset(dfn, 0, sizeof(dfn));
for(int i = 1; i <= n; i++)
if(!dfn[i])
tarjan(i);
}
void solve()
{
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(belong[u] != belong[v])
{
in[belong[v]]++;
out[belong[u]]++;
}
}
}
int in_cnt = 0, out_cnt = 0;
for(int i = 1; i <= scc_cnt; i++)
{
if(!in[i]) in_cnt++;
if(!out[i]) out_cnt++;
}
printf("%d\n%d\n", in_cnt, max(in_cnt, out_cnt));
}
int main(void)
{
while(cin >> n)
{
for(int i = 0; i < maxn; i++)
g[i].clear();
for(int i = 1; i <= n; i++)
{
int t;
while(cin >> t, t)
g[i].push_back(t);
}
find_scc();
if(scc_cnt == 1) printf("1\n0\n"); //注意特判只有一个连通分量
else solve();
}
return 0;
}