tarjan强连通分量 T4 有向图加边成为强连通图

许多学校与计算机网络相连。这些学校之间制定了协议:每所学校都有一份向其分发软件的学校名单(“接收学校”)。注意,如果B在学校A的分布列表中,那么A不一定出现在学校B的列表中 您需要编写一个程序,计算必须接收新软件副本的学校的最小数量,以便根据协议,软件能够到达网络中的所有学校(子任务a)。作为进一步的任务,我们希望确保通过将新软件的副本发送到任意一个学校,该软件将到达网络中的所有学校。为了实现这一目标,我们可能不得不扩大新成员的接收名单。计算必须进行的最小扩展数,以便我们将新软件发送到任何学校,它都将到达所有其他学校(子任务B)。一个扩展是指将一个新成员引入一个学校的接收名单。

第一行包含整数N:网络中的学校数(2 N 100)。学校由前N个正整数标识。接下来的N行中的每一行都描述了接收机列表。行i+1包含学校i的接收者的标识符。每个列表以0结尾。空列表在行中仅包含一个0。

你的程序应该向标准输出写两行。第一行应该包含一个正整数:子任务A的解。第二行应该包含子任务B的解。

input

5
2 4 3 0
4 5 0
0
0
1 0

output

1
2

思路

上一题做完能体会到tarjan算法的一般使用方法, 很多情况下都是tarjan接拓扑排序的思想, 关心出点和入点, 这一题也不例外。

题意是有一个有向边图, 第一个是求有多少点, 可以从该点出发到达所有点。 第二个是求需要连几条边, 使得该图为强连通图。

暴力做法遍历所有点第一个会超时, 第二个也没思路做, 这里直接考虑转化为 DAG(有向无环图)。 q为起点, Q为终点, 对于每个起点, 没有其他点能够到达q, 所以要想从某一点到达所有点, 就必须从q起点出发, 故很显然第一个问题就是起点(入度为0的点)数量。

对于第二个问题, 假设 能够到达 , 能够到达 。(只有两个起点和两个终点, 也可以还能够到达 ) 只需要将 链接, 就剩下一条 , 再首尾连一条即可, 这种链接方法把 2个起点2个终点 转化为 1个起点1个终点, 然后再 转化为 0个起点0个终点的强连通图。

显然, 类似于动态规划问题, 可以写一个转移方程: 代表起点/终点添加边的数量, 使得整个图为强连通图。 即 那么当起点先减到0时, 剩下需要添加的边数为 当终点先减到0时, 剩下需要添加的边数为 也就说, 最终答案就是 即起点数量和终点数量的最大值。

故问题1就是起点的数量, 问题2就是起点数量和终点数量的最大值。

代码

const int N = 1e5 + 10;
 
int h[N], e[N], ne[N], idx;
int stk[N], top;
bool in_stk[N];
int dfn[N], low[N], timestamp;
int n, m;
int id[N], scc_cnt;
int din[N], dout[N];
 
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
 
void tarjan(int u)
{
    dfn[u] = low[u] = ++timestamp;
    stk[++top] = u, in_stk[u] = true;
 
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])
        {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        }
        else if (in_stk[j])
            low[u] = min(low[u], dfn[j]);
    }
 
    if (dfn[u] == low[u])
    {
        ++scc_cnt;
        int y;
        do
        {
            y = stk[top--];
            in_stk[y] = false;
            id[y] = scc_cnt;
        } while (y != u);
    }
}
 
int main()
{
    FasterIO;
    cin >> n;
    mem(h, -1);
    for (int i = 1; i <= n; i++)
    {
        int t;
        while (cin >> t && t)
            add(i, t);
    }
 
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            tarjan(i);
 
    for (int i = 1; i <= n; i++)
    {
        for (int j = h[i]; ~j; j = ne[j])
        {
            int k = e[j];
            int a = id[i], b = id[k];
            if (a != b)
            {
                din[b]++;
                dout[a]++;
            }
        }
    }
 
    int a = 0, b = 0;
    for (int i = 1; i <= scc_cnt; i++)
    {
        if (!din[i])
            a++;
        if (!dout[i])
            b++;
    }
 
    cout << a << "\n";
    if (scc_cnt == 1)
        cout << 0 << endl;
    else
        cout << max(a, b) << endl;
 
    return 0;
}