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

news/2024/7/20 21:59:32 标签: 图论, 算法, 深度优先, 蓝桥杯

无向图的双连通分量

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

无向图的双连通分量

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

桥(割边)

AcWing 395. 冗余路径

#include <cstring>
#include <iostream>
#include <algorithm>

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 <iostream>
#include <cstring>

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 <iostream>
#include <cstring>
#include <vector>

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;
}

http://www.niftyadmin.cn/n/476645.html

相关文章

π-Disk®本地个人云

云计算和大数据发展至今,无论是节点数还是数据量,都达到了一个更高的层次。这意味着更多的计算能力要下沉到边缘侧,满足实时性和安全性的需求。为此,我们基于电脑手机等边缘计算设备, 打造了葫芦儿派盘本地个人云系列产品,获得了信创、教育等领域用户的广泛好评。 π-Di…

Debezium系列之:支持数据库存储系统,把ddl信息存储到数据库表中

Debezium系列之:支持数据库存储系统,把ddl信息存储到数据库表中 一、需求背景二、实现的效果三、核心参数四、创建存储ddl表五、创建采集表六、完整配置七、提交connector八、插入数据九、查看ddl数据十、修改表结构十一、清空存储ddl表十二、总结和延展一、需求背景 目前deb…

error: RPC failed; curl 28 OpenSSL SSL_read: Connection was reset, errno 10054

clone MiniGPT-4的时候报错 Cloning into MiniGPT-4... error: RPC failed; curl 28 OpenSSL SSL_read: Connection was reset, errno 10054 fatal: the remote end hung up unexpectedly解决办法 先 git config --global http.sslVerify "false"然后再clone就好了…

pytest的使用

Python标准库并不包含pytest&#xff0c;因此使用时需要先使用在终端使用如下命令来安装这个第三方包。安装完成后直接运行pytest可以查看到是否安装成功(在Linux系统上可能需要python3 -m pytest)。 $ python3 -m pip install --user pytest 或者 $ pip install pytest 测试文…

ElasticSearch-IK分词器介绍和下载

IK分词器 什么是IK分词器&#xff1f; 分词:把一段中文或者别的划分成一个一个的关键字,我们在搜索的时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,比如"我爱魏一鹤"会被分成&quo…

OAuth2:使用Gitee第三方授权

gitee OAuth2文档 1. 配置授权页url 传入client_id&#xff08;创建应用获得&#xff09;、redirect_uri&#xff08;需要通过第三方授权跳转的url&#xff09; https://gitee.com/oauth/authorize?client_id{client_id}&redirect_uri{redirect_uri}&response_type…

第三章 仅支持追加的单表内存数据库

第三章 仅支持追加的单表内存数据库 我们将从小处着手&#xff0c;对数据库施加很多限制。目前&#xff0c;它有如下限制&#xff1a; 支持两种操作&#xff1a;插入一行和打印所有行 仅驻留在内存中&#xff08;不需要持久化到磁盘&#xff09; 支持单个硬编码表 我们的硬…

Unity中audioSource播放指定的audio ——切换时带淡出效果

用到的包&#xff1a; using Cysharp.Threading.Tasks;代码&#xff1a; /// <summary>/// 播放指定的clip声音&#xff0c;当前有声音在播放&#xff0c;则淡出并停止&#xff0c;然后播放给定的声音/// 适用场景&#xff1a;有N个button&#xff0c;对应N个clip&#…