图论的连通性相关

强连通:有向图中,如果任意2点都相互可达,则该图是强连通图。

强连通分量:有向图中,其强连通图子图,称为强连通分量。(缩点后每个点都原图中最大的强连通分量)

一个有向图是强连通的,等价于G中有一个回路,它至少包含每个节点一次。(只是一笔画经过所有点回到原点,点可以通过多次,不一定是一个大环,也可能是几个小环的拼接。但环上的所有点一定构成强连通分量)。

 

 

一些问题只要变成有向无环图就容易解决,但其中有环就比较难办,而环等价于强连通分量,把每个强连通分量缩成一个点,就是dag了。

 

常用算法是tarjan算法,复杂度是O(n+m),线性的。(注意有很多个tarjan算法..这个是求强连通的,还有离线求lca的,求双联通的..)

 

模板比较简单而且不怎么灵活,主要是一些推论。

比如一些简单推论:

从任一点出发都可以到达的点有几个?

缩点后如果出度为0的点集唯一,符合条件的点就是该点集中的点,否则不存在符合条件的点。(poj 2186)

 

最小点基:选择最少的点,使得从这些点出发可以到达所有点。

最小权点基:选择权和尽量小的点集,使得从这些点出发可以到达所有点。(hdu5934)

解法:入度为0的强连通分量个数即为最小点基,从每个入度为0的强连通分量中取出权值最小的点,构成的集合即最小权点基。

 

最少加多少边能使图变为强连通图?(poj1236)

Ans = max(缩点后入度为0的点集,缩点后出度为0的点集)(特判如果缩点后只有一个点则原图已经强连通,ans = 0)

 

 

以poj1236为例(求最小点基点数,及至少加几个点变成强连通图),比较模板的写法(主函数中只需要调用,缩点后的信息都有直接处理好,加边后跑一遍当做黑盒用就好..)

#include <bits/stdc++.h>

#define ll long long

#define mm 10005

using namespace std;

stack<int> sta;

bool vis[mm], in[mm], out[mm];

//vis点是否在栈中 缩点后的点是否有出度入度

int n, m, tim, num, cnt;

//tim点的标记 num强连通分量个数(缩点后的点数) cnt链表计数 都是从1开始

int h[mm], dfn[mm], low[mm], siz[mm], bel[mm];

//bel:u是属于哪个集合中的 siz[i]第i个强连通的点数

int xx[mm], yy[mm];//备份边

struct

{

    int to, ne;

} ed[mm * 5];

void init()

{

    memset(vis, 0, sizeof(vis)), memset(in, 0, sizeof(in));

    memset(h, 0, sizeof(h)), memset(dfn, 0, sizeof(dfn));

    memset(low, 0, sizeof(low)), memset(bel, 0, sizeof(bel));

    memset(siz, 0, sizeof(siz)), memset(out, 0, sizeof(out));

    cnt = tim = num = 0;

    while (!sta.empty())

        sta.pop();

}

void add(int u, int v)

{

    ed[++cnt].to = v, ed[cnt].ne = h[u], h[u] = cnt;

    xx[cnt] = u, yy[cnt] = v;

}

void tarjan(int u)

{

    dfn[u] = low[u] = ++tim;

    vis[u] = 1;

    sta.push(u);

    for (int i = h[u]; i; i = ed[i].ne)

    {

        int v = ed[i].to;

        if (!dfn[v])

            tarjan(v), low[u] = min(low[u], low[v]);

        else if (vis[v])

            low[u] = min(low[u], dfn[v]);

    }

    if (dfn[u] == low[u])//发现新的强连通分量

    {

        int v;

        num++;

        do

        {

            v = sta.top(), sta.pop();

            vis[v] = 0, bel[v] = num;

            siz[num]++;//num从1开始

        } while (u != v);

    }

}

int main()

{

    int i, j, te;

    while (~scanf("%d", &n))

    {

        init();

        for (i = 1; i <= n; i++)

            while (scanf("%d", &te) && te)

                add(i, te);

        for (i = 1; i <= n; i++)

            if (!dfn[i])

                tarjan(i);

        //缩点后共num个点 i缩点后为bel[i] 缩点后第x个点中有原siz[x]个点

        for (i = 1; i <= cnt; i++)

            if (bel[xx[i]] != bel[yy[i]])

                out[bel[xx[i]]] = 1, in[bel[yy[i]]] = 1;

        //点集bel[x[i]]有出度 bel[y[i]]有入度

        if (num == 1)

        {

            puts("1\n0");

            continue;

        }

        int ans1 = 0, ans2 = 0;

 

        for (i = 1; i <= num; i++)

        {

            if (in[i] == 0)

                ans1++;

            if (out[i] == 0)

                ans2++;

        }

        printf("%d\n%d\n", ans1, max(ans1, ans2));

    }

    return 0;

}

 

 

 

 

计蒜客 百度科学家(中等)直接建图缩点找出度为0的点集,算出每个点集的权和即可(然而ac不了数据极少的简单版...有点谜 困难版据说是可持久化线段树优化建图..)

 

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/weixin_40191952/article/details/88982820