算法提高-图论- 无向图的双连通分量

无向图的双连通分量

  • 无向图的双连通分量
    • 桥(割边)
      • AcWing 395. 冗余路径
    • 割点
      • AcWing 1183. 电力
      • AcWing 396. 矿场搭建

无向图的双连通分量

本篇章的内容我的学习大多已开在算法进阶指南这本书和题解(算法进阶指南中有关搜索树的概念解释的特别好),主要笔记都在算法进阶指南中,代码上传的是一篇题解里面的,这位博主的注释写的特别好

桥(割边)

AcWing 395. 冗余路径

#include 
#include 
#include 

using namespace std;

const int N = 5010, M = 20010;

int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int id[N], dcc_cnt;
bool is_bridge[M];//每条边是不是桥
int d[N];//度数

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}

void tarjan(int u, int from)
{
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top] = u;

    for (int i = h[u]; i!=-1; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])//j未遍历过
        {
            tarjan(j, i);//dfs(j)
            low[u] = min(low[u], low[j]);//用j更新u
            if (dfn[u] < low[j])//j到不了u
                // 则x-y的边为桥,
                //正向边is_bridge[i] 反向边is_bridge[i ^ 1]都是桥
                is_bridge[i] = is_bridge[i ^ 1] = true;
                // 这里i==idx 如果idx==奇数 则反向边=idx-1 = idx^1
                //            如果idx==偶数 则反向边=idx+1 = idx^1
        }
        // j遍历过 且i不是反向边(即i不是指向u的父节点的边)
        // 因为我们不能用u的父节点的时间戳更新u
        else if (i != (from ^ 1))
            low[u] = min(low[u], dfn[j]);
    }
    //双连通分量起点u  /
    //                u
    //               /
    //              ne1
    //             ne2 
    if (dfn[u] == low[u])
    {
        ++ dcc_cnt;
        int y;
        do {
            y = stk[top -- ];
            id[y] = dcc_cnt;
        } while (y != u);
    }
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }

    tarjan(1, -1);//防止搜反向边 用一个from

    for (int i = 0; i < idx; i ++ )
        //如果边i是桥 在其所连的出边的点j所在强连通分量的度+1
        // 桥两边的双连通分量各+1
        if (is_bridge[i])
            d[id[e[i]]] ++ ;

    int cnt = 0;
    for (int i = 1; i <= dcc_cnt; i ++ )
        if (d[i] == 1)//多少个度数为1的节点(强连通分量)
            cnt ++ ;//需要加的边的数量

    cout << (cnt + 1) / 2 << endl;

    return 0;
}

割点

AcWing 1183. 电力

#include 
#include 

using namespace std;

const int N = 1e4 + 10, M = 2 * 1e5 + 10;
int n, m;
int root;
int h[N], ne[M], e[M], idx;
int dfn[N], low[N], timestamp;
int dcc_cnt;
int tree_ans;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void tarjan(int u)
{
    int trees = 0;//当前块内可以分出来的子树个数
    dfn[u] = low[u] = ++ timestamp;
    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]);
            if (low[j] >= dfn[u]) trees ++ ;
        }
        else low[u] = min(low[u], dfn[j]);
    }
    if (u != root) trees ++;
    tree_ans = max(tree_ans, trees);
}

int main()
{
    while (cin >> n >> m, n || m)
    {
        tree_ans = 0;
        dcc_cnt = 0;
        memset(dfn, 0, sizeof dfn);
        memset(low, 0, sizeof low);
        memset(h, -1 ,sizeof h);
        idx = 0;
        while (m -- )
        {
            int a, b;
            cin >> a >> b;
            add(a, b), add(b, a);
        }

        for (root = 0; root < n; root ++ )
        {
            if (!dfn[root])
            {
                dcc_cnt ++;
                tarjan(root);
            }
        }
        cout << dcc_cnt + tree_ans - 1 << endl;
    }
    return 0;
}

AcWing 396. 矿场搭建

#include 
#include 
#include 

using namespace std;

const int N = 1010, M = 1010;

int m, n;
int root;
int dfn[N], low[N], timestamp;
int h[N], e[M], ne[M], idx;
vector<int> dcc[N];
int dcc_cnt;
bool cut[N];
int stk[N], top;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void tarjan(int u)
{
    low[u] = dfn[u] = ++ timestamp;
    stk[ ++ top] = u;
    //1.是孤立点
    if (u == root && h[u] == -1)
    {
        ++ dcc_cnt;
        dcc[dcc_cnt].push_back(u);
       //不影响 top -- ;
        return ;
    }
    //2.不是孤立点
    int son = 0;
    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]);
            if (dfn[u] <= low[j])//找到割点了,一个割点属于多个联通块,因此找到割点后立马进行联通块的记录
            {
                son ++ ;
                
                if (u != root || son > 1) cut[u] = true;
                ++ dcc_cnt;
                int y ;
                do{
                    y = stk[top -- ];
                    dcc[dcc_cnt].push_back(y);
                }while (y != j);
                dcc[dcc_cnt].push_back(u);
            }
        }
        else low[u] = min(low[u], dfn[j]);
    }
}


int main()
{
    int T = 1;
    while (cin >> m, m)
    {
        memset(h, -1, sizeof h);
        memset(dfn, 0, sizeof dfn);
        memset(cut,0,sizeof cut);
        for (int i = 1; i <= dcc_cnt; i ++) dcc[i].clear();
        n = dcc_cnt = idx = top = timestamp = 0;//n也要记得初始化,题目没给n但是给了各个点的编号,得自己比对
        while (m -- )
        {
            int a, b;
            cin >> a >> b;
            n = max(n, a), n = max(n, b);
            add(a, b), add(b, a);
        }
        for (root = 1; root <= n; root ++ )
        {
            if (!dfn[root]) tarjan(root);
        }

        int res = 0;//最少新增几个出口
        unsigned long long num = 1;//方案数,题目说了2^64以内

        for (int i = 1; i <= dcc_cnt; i ++ )
        {
            int cnt = 0;
            //遍历每个联通块里面的点
            for (int j = 0; j < dcc[i].size(); j ++ )
            {
                if (cut[dcc[i][j]] == true)
                {
                    cnt ++ ;
                }
            }
            if (cnt == 0) 
            {
                if(dcc[i].size()>1)res+=2,num*=dcc[i].size()*(dcc[i].size()-1)/2;
                else res++;
            }
            else if (cnt == 1) 
            {
                res += 1;
                num *= dcc[i].size() - 1;
            }
            //cnt >= 2的话不用添加出口
        }
        printf("Case %d: %d %llu\n", T ++, res, num);
    }
    return 0;
}

你可能感兴趣的:(图论,算法,深度优先,蓝桥杯)