B - Network UVA - 割点模板

  • B - Network

  •  UVA - 315 
  • 我们维护两个数组dfn[]和low[],dfn[u]表示顶点u第几个被(首次)访问,low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],此时u就是割点。
  • 但这里也出现一个问题:怎么计算low[u]。
  • 假设当前顶点为u,则默认low[u]=dfn[u],即最早只能回溯到自身。
  • 有一条边(u, v),如果v未访问过,继续DFS,DFS完之后,low[u]=min(low[u], low[v]);
  • 如果v访问过(且u不是v的父亲),就不需要继续DFS了,一定有dfn[v]<dfn[u],low[u]=min(low[u], dfn[v])。
  • 转自:https://blog.csdn.net/duan_1998/article/details/76690536?utm_source=copy 
  • 图一: 
    这里写图片描述 
    这里的图一共有三个割点,编号分别是2 ,3,4,而去掉1或5,整个网络体系依旧是一个连通图。图二: 
     
    此时在图一上加了一条红线,也即是将2,4连通,那么此时3便不是割点了,因为去掉3之后,可以通过2->4将整个图连通。 
    如果用深搜的思想去写,那么需要一个起点,也可以称为根节点,最终,整个图都会被遍历一遍,假设在这里,设点1为根节点(谁都可以)(无向图),那么整个遍历顺序就是:1->2->3->4->5,这样标记了编号之后,深搜经过的边就会构成名叫:深搜优先生成树的树形结构(树形结构不会有环)。也即是每个点都有一个访问时刻(num数组),那么分析一下存在割点的情况: 
    第一种:假设当前点围割点,那么访问时刻比它大的点与访问时刻比他小的点之间没有联系。 
    对于这一种情况,图一中的点3符合,而图二的点3不符合,明显看到2-4有条线跳过了3,那么我们该怎么判断当前点有没有被跳过呢?(点的编号越小,越靠近根哦) 
    主要还是那条红线,这条线并没有出现在深搜优先生成树中,所以叫做:反祖边(回退边),也就是能够从先序编号较大的点直接回到编号较小的点,所以根据这条红线,弄了一个数组low,low[4]=min(low[4],num[2]),所以由这个数组记录可以知道,4最高是可以回到点2的,所以去掉点3无所谓,所以在整个代码中,先序编号(num数组)记录了是整个深度优先生成树,他是连通的,而low数组记录的是当前点可以返回到编号最小的那个点的先序编号,比较的时候呢,我们可以比较low数组,比如:low[3]>low[4],可知,点4可以比3更接近根,所以可以跳过3,也就是说,去掉3,4依旧可以与比3更高的点相连接,使网络连通。
  • 图三: 
     
    那么,就像图三一样,如果真要一个个比较的话,也要比较low[3]与low[5]的关系,如果3的子节点有很多,那完了,铁定超时,所以我们可以直接把点3的所有子节点的low最小值赋给low[3],那样,当深搜回溯的时候,遇到3,如果此时low[3]是1(low[5]给的),那么和他自身的先序编号num[3]进行比较,说明他的子节点low的值有比它小的,也就是他的子节点与3上面的点有条边叫做反祖边(回退边),那说明3肯定不是割点。至于怎么赋值,可以在回溯的时候赋值呀。 
    但是呢,除此之外,还要特判这个点不是根节点,因为没有编号比根节点更小的了。。。你说是吧。。。 
    所以根节点还需要特判,怎么特判呢? 
    既然是树形结构,那么不存在环,所以当根节点的儿子数量大于1的时候,就说明他是割点了 
  • #include<bits/stdc++.h>
    using namespace std;
    #define maxn 10010
    int dfn[maxn],low[maxn],sum,n;
    int parent[maxn],ans[maxn],cnt;
    vector<int>mmp[maxn];
    bool vis[maxn],check[maxn];
    void dfs(int u)
    {
        int children=0;
        dfn[u]=low[u]=++cnt;
        vis[u]=true;
        int len=mmp[u].size();
        for(int i=0; i<len; i++)
        {
            int v=mmp[u][i];
            if(!vis[v])
            {
                children++;
                parent[v]=u;
                dfs(v);
                low[u]=min(low[u],low[v]);
                if(parent[u]==-1&&children>=2&&!check[u])
                {
                    sum++;
                    check[u]=1;
                }
                else if(parent[u]!=-1&&low[v]>=dfn[u]&&!check[u])
                {
                    sum++;
                    check[u]=1;
                }
            }
            else if(v!=parent[u])
                low[u]=min(low[u],dfn[v]);
        }
    }
    int main()
    {
        while(~scanf("%d",&n)&&n)
        {
            memset(low,0,sizeof(low));
            memset(parent,-1,sizeof(parent));
            memset(dfn,0,sizeof(dfn));
            memset(vis,0,sizeof(vis));
            memset(check,0,sizeof(check));
            for(int i=1; i<=n; i++)
                mmp[i].clear();
            int a,c;
            while(~scanf("%d",&a)&&a)
            {
                while(getchar()!='\n')
                {
                    scanf("%d",&c);
                    mmp[a].push_back(c);
                    mmp[c].push_back(a);
                }
            }
            cnt=sum=0;
            dfs(1);
            printf("%d\n",sum);
        }
        return 0;
    }
    

     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值