avatar
fireworks99
keep hungry keep foolish

POJ 1236 Network of Schools(强连通+缩点)

缩点

缩点就是要将两两之间可以相互到达的点,缩成一个点来表示(即求无向图的连通分量,或者有向图的强连通分量),他们的求法都是相同的,都是将无向图转化为有向图。

Trajan算法求缩点

stack\ st;

int num;///num:强连通分量(连通块)的个数,缩点后点的个数

bool vis[105];///该点是否访问过

int inst[105];///该点是否在栈中

int time[105];///i点第一次被访问的时间

int low[105];///i点所在强连通分量子图中第一个被搜到的点的time值

int ltt[105];///i点属于第几个强连通分量(0表示不属于任何一个)

强连通分量内元素个数最少为1(该元素本身),这意味着每个元素都有从属的强连通分量

Code(Trajan)

void Trajan(int x)///搜索x点及其子树
{
    int temp;
    st.push(x);
    inst[x] = vis[x] = 1;
    low[x] = time[x] = ++t;
    for(int i = 0; i < v[x].size(); i++)
    {
        temp = v[x][i];
        if(vis[temp] == 0)
        {
            Trajan(temp);
            low[x] = min(low[x], low[temp]);
        }
        else if(inst[temp] == 1)///成环时
            low[x] = min(low[x], time[temp]);
        else///点temp访问过却不在当前栈里:属于已发现的强连通分量
            continue;///这两个不能合二为一的原因:只有从此指彼的单向路
    }
    if(low[x] == time[x])
    {
        num++;///找到第num个强连通分量
        while(!st.empty())
        {
            temp = st.top();
            st.pop();
            ltt[temp] = num;///temp结点属于第num个强连通分量
            inst[temp] = 0;
            if(temp == x)
                break;
        }
    }
}

POJ 1236

n所学校,它们通过单向边连接,如果A—>B表示A学校可以传递信息给B学校

问题一:至少要向几个学校传递信息,才能保证所有学校都能收到信息;

问题二:至少要添加多少组关系,才能保证给任意一个学校原始信息后,其他所有学校都能收到信息

Analyze

问题一:缩点后数出入度为0的点的个数

问题二:缩点后求再次完成强连通最少添加几条边max(ain, aout)

Code

#include <stack>
#include <vector>
#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;

stack<int> st;
vector<int> v[105], nv[105];
int n, t, num;///num:强连通分量(连通块)的个数,缩点后点的个数
int ain, aout;

bool vis[105];///该点是否访问过
int inst[105];///该点是否在栈中

int time[105];///i点第一次被访问的时间
int low[105];///i点所在强连通分量子图中第一个被搜到的点的time值

int ltt[105];///i点属于第几个强连通分量(0表示不属于任何一个)
int in[105], out[105];

void Trajan(int x)///搜索x点及其子树
{
    int temp;
    st.push(x);
    inst[x] = vis[x] = 1;
    low[x] = time[x] = ++t;
    for(int i = 0; i < v[x].size(); i++)
    {
        temp = v[x][i];
        if(vis[temp] == 0)
        {
            Trajan(temp);
            low[x] = min(low[x], low[temp]);
        }
        else if(inst[temp] == 1)///成环时
            low[x] = min(low[x], time[temp]);
        else///点temp访问过却不在当前栈里:属于已发现的强连通分量
            continue;///这两个不能合二为一的原因:只有从此指彼的单向路
    }
    if(low[x] == time[x])
    {
        num++;///找到第num个强连通分量
        while(!st.empty())
        {
            temp = st.top();
            st.pop();
            ltt[temp] = num;///temp结点属于第num个强连通分量
            inst[temp] = 0;
            if(temp == x)
                break;
        }
    }
}

///强连通分量内元素个数最少为1(该元素本身)
///这意味着每个元素都有从属的强连通分量

int main()
{
    int x, temp;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        while(scanf("%d", &x), x != 0)
            v[i].push_back(x);
    }

    t = num = 0;
    for(int i = 1; i <= n; i++)
        if(vis[i] == 0)
            Trajan(i);

    for(int i = 1; i <= n; i++)
        for(int j = 0; j < v[i].size(); j++)
        {
            temp = v[i][j];
            if(ltt[i] != ltt[temp])
                nv[ltt[i]].push_back(ltt[temp]);
        }

    for(int i = 1; i <= num; i++)
        for(int j = 0; j < nv[i].size(); j++)
            in[nv[i][j]]++, out[i]++;

    ain = aout = 0;
    for(int i = 1; i <= num; i++)
    {
        if(in[i] == 0)
            ain++;
        if(out[i] == 0)
            aout++;
    }

    printf("%d\n", ain);
    if(num == 1)
        printf("0\n");
    else
        printf("%d\n", max(ain, aout));
        ///让所有缩点形成一个强连通图
        ///假设 aout = max(ain, aout)
        ///那么恰当选择(aout - 1)个出度为0的点逆向添边会减少缩点,最终形成一条单向链
        ///此时按照链的方向添一条连接首尾的边就形成强连通图(一个标准有向环)
    return 0;
}

参考1: https://blog.csdn.net/jaihk662/article/details/52194288

参考2: https://blog.csdn.net/nhl19961226/article/details/79114287

Kosaraju求缩点

将边反向建立后,强连通分量内的点,其可达性不受影响。

但不能沿着边访问到除了这个强连通分量之外的点

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAX_V = 10005;

int V, ain, aout, in[MAX_V], out[MAX_V];
vector<int> vs;
vector<int> G[MAX_V];
vector<int> rG[MAX_V];
vector<int> nv[MAX_V];

bool used[MAX_V];
int cmp[MAX_V];

void add_eage(int from, int to)
{
    G[from].push_back(to);
    rG[to].push_back(from);
}

void DFS(int v)
{
    used[v] = 1;
    for(int i = 0; i < G[v].size(); ++i)
        if(!used[ G[v][i] ])
            DFS(G[v][i]);
    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)
        if(!used[ rG[v][i] ])
            rDFS(rG[v][i], k);
}

int scc()
{
    memset(used, 0, sizeof(used));
    vs.clear();
    for(int v = 1; v <= V; ++v)
        if(!used[v])
            DFS(v);
    memset(used, 0, sizeof(used));
    int k = 0;
    for(int i = vs.size() - 1; i >= 0; --i)
        if(!used[vs[i]])
            rDFS(vs[i], ++k);
    return k;
}

int main()
{
    int x, temp;
    scanf("%d", &V);
    for(int i = 1; i <= V; i++)
    {
        while(scanf("%d", &x), x != 0)
            add_eage(i, x);
    }

    int num = scc();

    for(int i = 1; i <= V; i++)
        for(int j = 0; j < G[i].size(); j++)
        {
            temp = G[i][j];
            if(cmp[i] != cmp[temp])
                nv[cmp[i]].push_back(cmp[temp]);
        }

    for(int i = 1; i <= num; i++)
        for(int j = 0; j < nv[i].size(); j++)
            in[nv[i][j]]++, out[i]++;

    ain = aout = 0;
    for(int i = 1; i <= num; i++)
    {
        if(in[i] == 0)
            ain++;
        if(out[i] == 0)
            aout++;
    }

    printf("%d\n", ain);
    if(num == 1)
        printf("0\n");
    else
        printf("%d\n", max(ain, aout));
    return 0;
}
Site by Baole Zhao | Powered by Hexo | theme PreciousJoy