• 【POJ2117】Electricity-点双连通分量


    测试地址:Electricity
    题目大意:给定一个无向图(注意:不一定连通),问从里面去掉一个顶点以及与其相连的所有边之后,整个图被划分成的连通块的最大数目。
    做法:本题需要用到点双连通分量的知识。
    什么叫点双连通分量呢?首先,一个无向图是点双连通的,就代表将这个图中任意一点去掉,剩下的图仍能连通,那么类比强连通和强连通分量的定义,不难得出点双连通分量的定义:对一个无向图G的两个子图G0,若G0是点双连通的,而包含G0的任意其他的G的子图都不是点双连通的,那么G0就是图G的一个点双连通分量。求点双连通分量可以使用类似强连通分量的Tarjan算法来做,这个可以自己去查资料,这里就不细说了。
    定义了解之后,对于这一题我们分析一下,一般来说一个顶点只从属于一个点双连通分量,而割点可以从属于多个点双连通分量,我们知道将割点从图中去掉之后,图就会被分为多个连通块,而被分成的连通块的数目正好就是这个割点从属的点双连通分量的数目。那么怎么算出一个割点从属的点双连通分量数目呢?很简单,假设点u在DFS树中的儿子节点vlow[v]中,大于等于dfn[u]的有s个,那么这个点就从属于s+1个点双连通分量。特别地,对于DFS树的根,它只从属于s个点双连通分量。这个应该很容易看得出来了,因为我们判断割点的时候,一旦出现满足上述条件的情况,这个点就是割点,同时也可以作为一个点双连通分量的根,那么如果上述情况出现了s次,这个点就可以作为s个点双连通分量的根,而这个点不作为点双连通分量根的情况也有一种(和它在DFS树中的父亲节点同属一个点双连通分量),注意这种情况当该点为DFS树的根时是不存在的,所以答案对于非根节点就是s+1,对于根节点是s。那么我们只需要统计每个点所从属的点双连通分量数目的最大值,这个最大值就是答案。
    然而本题还没有结束,注意到原图可能不连通,那么设原图有cnt个连通块,上述求出的答案是ans,显然正确的答案是ans+cnt1。这样我们就解决了这个问题。
    以下是本人代码:

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    int n,m,tot,tim,ans,rt,first[10010],dfn[10010],low[10010];
    struct edge {int v,next;} e[2000010];
    bool vis[10010];
    
    void insert(int a,int b)
    {
        e[++tot].v=b;
        e[tot].next=first[a];
        first[a]=tot;
    }
    
    void dfs(int v)
    {
        vis[v]=1;
        dfn[v]=++tim;
        low[v]=dfn[v];
        int s=1;
        for(int i=first[v];i;i=e[i].next)
        {
            if (!vis[e[i].v])
            {
                dfs(e[i].v);
                if (low[e[i].v]>=dfn[v]) s++;
                low[v]=min(low[v],low[e[i].v]);
            }
            else low[v]=min(low[v],dfn[e[i].v]);
        }
        if (v!=rt) ans=max(ans,s);
        if (v==rt) ans=max(ans,s-1);
    }
    
    void tarjan()
    {
        memset(vis,0,sizeof(vis));
        tim=0;
        int cnt=0;
        for(int i=0;i<n;i++)
            if (!vis[i])
            {
                cnt++;
                rt=i;
                dfs(i);
            }
        ans+=cnt-1;
    }
    
    int main()
    {
        while(scanf("%d%d",&n,&m)&&n)
        {
            memset(first,0,sizeof(first));
            tot=0;
            for(int i=1;i<=m;i++)
            {
                int a,b;
                scanf("%d%d",&a,&b);
                insert(a,b),insert(b,a);
            }
            ans=0;
            tarjan();
            printf("%d
    ",ans);
        }
    
        return 0;
    }
    
  • 相关阅读:
    Android笔记之开机自启
    Android笔记之广播
    Hive笔记之collect_list/collect_set(列转行)
    Hive笔记之数据库操作
    hive笔记之row_number、rank、dense_rank
    Linux Shell管道调用用户定义函数(使shell支持map函数式特性)
    Linux shell爬虫实现树洞网鼓励师(自动回复Robot)
    分享一些免费的接码平台(国外号码)
    爬虫技能之内容提取:如何从有不可见元素混淆的页面中抽取数据
    ctf writeup之程序员密码
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793608.html
Copyright © 2020-2023  润新知