POJ1235 强连通分量拆分

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luke2834/article/details/82713186

题意:

  • 有N个学校,每个学校之间单向可以发送软件,现在给你一些学校之间的收发关系。问你下面两个问题:至少要给多少个学校发送软件才能使得最终所有学校都收到软件;至少要多加多少个关系才能使得向任意一个学校发送一套软件,每个学校都能收到软件。

思路:

  • 先进行强连通分量拆解,构成新的DAG图
  • 在图上找到,入度为0节点的个数,记为n1,出度为0节点的个数,记为n2
  • 任务1的答案是n1,任务2的答案是·max(n1, n2)
  • 任务1的答案很好理解,我们简单解释一下任务2正确的原因
  • 首先,达到要求的下界,是max(n1, n2),这是因为如果想要任意两点有通路,那么至少每个点都要有出度和入度(必要条件),通过把出度为0的点和入度为0的点直接加边,我们可以在至少加max(n1, n2)边的情况下,满足我们的必要条件。
  • 接下来我们说明可以构造一种方法,使得加max(n1, n2)条边就可以,满足我们任务2的要求,那么综合来看max(n1, n2)就是答案了。
  • 先假设,所有点都是位于同一个弱联通分量里的,容易知道必然存在一个入度为0的节点u可以到达所有出度为0的节点。任意找一个出度为0的节点vu连边(v, u)。再找一对出度0和入度0的节点对(a, b),要求他们直接ab有通路,加边(b, a)。重复上一步骤至结束。注意每次找的节点对,是尽可能之前没用过的(由于出度0和入度0的节点个数不一样多,尽可能是指保证多的那边不能重复使用)。这样就构建了一个大强连通图了。(具体可以画个图理解)
  • 在看多个弱联通分量的情况,取两个弱联通分量,加两条边。设两个联通分量里的uv分别记为u1v1u2v2,加边(v1, u2)(v2, u1),就合成了一个弱联通分量。都合并后,再按刚才的思路做,由于(v1, u2)(v2, u1)替代了(v1, u1)(v2, u2)两条边,所以合并并不会引入新的边,故得证。

实现:

#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 105;
vector<int> g[maxn];
vector<int> rg[maxn];
bool vis[maxn];
int order[maxn];
bool pre[maxn];
bool suf[maxn];
vector<int> tmp;
int n;

void dfs(int u){
    for (int i = 0; i < g[u].size(); i++){
        int v = g[u][i];
        if (vis[v])
            continue;
        vis[v] = true;
        dfs(v);
    }
    tmp.push_back(u);
}
void rdfs(int u, int k){
    order[u] = k;
    for (int i = 0; i < rg[u].size(); i++){
        int v = rg[u][i];
        if (vis[v])
            continue;
        vis[v] = true;
        rdfs(v, k);
    }
}
void add(int u, int v){
    g[u].push_back(v);
    rg[v].push_back(u);
}
int scc(){
    for (int i = 1; i <= n; i++){
        if (!vis[i]){
            vis[i] = true;
            dfs(i);
        }
    }
    memset(vis, 0, n+1);
    int k = 0;
    for (int i = n - 1; i >= 0; i--){
        if (!vis[tmp[i]]){
            vis[tmp[i]] = true;
            rdfs(tmp[i], k++);
        }

    }
    return k;
}

int main(){
    ios::sync_with_stdio(false);
    cin>>n;
    for (int i = 1; i <= n; i++){
        int v = -1;
        while (true){
            cin>>v;
            if (v == 0)
                break;
            add(i, v);
        }
    }
    int k = scc();
    if (k == 1){
        cout << 1 << endl << 0 << endl;
        return 0;
    }
    for (int i = 1; i <= n; i++){
        for (int j = 0; j < g[i].size(); j++){
            int v = g[i][j];
            if (order[i] != order[v]){
                pre[order[v]]= true;
                suf[order[i]] = true;
            }   
        }
    }
    int cnt_pre = 0, cnt_suf = 0;
    for (int i = 0; i < k; i++){
        if (pre[i] == false)
            cnt_pre++;
        if (suf[i] == false)
            cnt_suf++;
    }
    cout << cnt_pre << endl << max(cnt_suf, cnt_pre)<< endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/luke2834/article/details/82713186