POJ 1236 SCC 分解

58 篇文章 0 订阅
题意

传送门 POJ 1236

题解

两次 D F S DFS DFS 进行强连通分量分解,并记录其拓扑序。遍历原图,对端点属于不同强连通分量的边在新图进行连边,就实现了将分解后的强连通分量缩成一个顶点,此时得到了一个 D A G DAG DAG(有向无环图)。

子任务 A A A 需要发送最少次数以遍历全部学校。对于入度为 0 0 0 的点,必须发送一次; D A G DAG DAG 中任一入度非 0 0 0 的节点,存在至少一个拓扑序小于这个节点且能到达这个节点的入度为 0 0 0 的节点;那么答案即 D A G DAG DAG 入度为 0 0 0 的节点数量。

子任务 B B B 需要添加最少数量的边,使原图变成一个强连通分量。考虑到图可能不连通,设图中存在 k k k D A G DAG DAG,设第 i i i D A G DAG DAG 入度为 0 0 0 的节点数为 n i n_i ni,出度为 0 0 0 的节点数为 m i m_i mi。需要将第 i i i D A G DAG DAG 添加边使之变成两个强连通分量(即缩点后变成一条有向边及两端端点),最少需要 m a x ( n i , m i ) − 1 max(n_i,m_i)-1 max(ni,mi)1 条边(考虑 m i m_i mi 2 2 2 的情况,设这两个节点分别为 u , v u,v u,v 则可以添加一条连接 u u u 以及可达 v v v 的入度为 0 0 0 的节点,添加一条连接 v v v 以及可达 u u u 的入度为 0 0 0 的节点,此时 u , v u,v u,v 相互可达;其他情况可用类似方法实现添边,得到强连通分量后删去所添的其中一条边,就得到了目标情况);最后添加 k k k 条边使整个图变成一个强连通分量。则答案为 ∑ i = 1 k m a x ( n i , m i ) \sum\limits_{i=1}^{k} max(n_i,m_i) i=1kmax(ni,mi)

考虑到对于原本就为一个强连通分量的图,缩点后入度、出度为 0 0 0 的点是同一个节点,显然此时无需添加边,故特判之。对缩点得到的 D A G DAG DAG 按照有向边统计入度、出度,建图时按照无向图连边,那么 D F S DFS DFS 即可求解子任务 A , B A,B A,B

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
#define maxn 105
int n, cmp[maxn], indeg[maxn], outdeg[maxn];
vector<int> G[maxn], rG[maxn], sG[maxn], vs;
bool used[maxn];

void add_edge(int u, int v)
{
    G[u].push_back(v);
    rG[v].push_back(u);
}

void dfs(int v)
{
    used[v] = 1;
    for (int i = 0; i < G[v].size(); i++)
    {
        int u = G[v][i];
        if (!used[u]) dfs(u);
    }
    vs.push_back(v);
}

void rdfs(int v, int k)
{
    used[v] = 1, cmp[v] = k;
    for (int i = 0; i < rG[v].size(); i++)
    {
        int u = rG[v][i];
        if (!used[u]) rdfs(u, k);
    }
}

void sdfs(int v, int &n1, int &n2)
{
    used[v] = 1;
    n1 += !indeg[v] ? 1 : 0, n2 += !outdeg[v] ? 1 : 0;
    for (int i = 0; i < sG[v].size(); i++)
    {
        int u = sG[v][i];
        if (!used[u]) sdfs(u, n1, n2);
    }
}

int main()
{
    scanf("%d", &n);
    for (int u = 0; u < n; u++)
    {
        int v;
        while (~scanf("%d", &v) && v)
        {
            --v;
            add_edge(u, v);
        }
    }
    memset(used, 0, sizeof(used));
    for (int u = 0; u < n; u++)
    {
        if (!used[u]) dfs(u);
    }
    memset(used, 0, sizeof(used));
    int k = 0;
    for (int i = vs.size() - 1; i >= 0; i--)
    {
        int u = vs[i];
        if (!used[u]) rdfs(u, k++);
    }
    memset(indeg, 0, sizeof(indeg));
    memset(outdeg, 0, sizeof(outdeg));
    for (int u = 0; u < n; u++)
    {
        int c = cmp[u];
        for (int i = 0; i < G[u].size(); i++)
        {
            int c2 = cmp[G[u][i]];
            if (c == c2) continue;
            sG[c].push_back(c2);
            sG[c2].push_back(c);
            ++outdeg[c], ++indeg[c2];
        }
    }
    memset(used, 0, sizeof(used));
    int r1 = 0, r2 = 0;
    for (int u = 0; u < k; u++)
    {
        int n1 = 0, n2 = 0;
        if (!used[u])
        {
            sdfs(u, n1, n2);
        }
        r1 += n1, r2 += max(n1, n2);
    }
    printf("%d\n%d\n", r1, k == 1 ? 0 : r2);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值