割点与割边

◇◆◇概念◇◆◇

联通块:任意两个点之间存在至少一条路径可以到达对方的一个点集。
说白了就是某一块里面的点是连起来的。

割边:割掉某一条边能使整个图分为两个联通块的边,又称关键边(桥)。

割点:割掉某一个点(这意味着失去所有连向这个点的边)使整个图分为两个联通块的点。

◇◆◇目标◇◆◇
找出一个无向图中的割点、割边

◇◆◇怎么找◇◆◇
先在这里插入两个概念。
在我们DFS的时候,假设我们现在在结点u,u有一条边通向v.
树边:如果v没有被访问过,那么DFS(v),边(u,v)为一条树边。
回边(反祖边):如果v被访问过,那么说明v的编号比u大,边(u,v)为一条反祖边。

这里写图片描述

①确定遍历图的方式:DFS深度优先搜索
目的:将图变成一颗DFS树
②利用DFS对遍历到的点进行编号
用dfn[i]来表示第i号结点是第几个被遍历到的。
③!对于每一个点u,定义low[]。
low[u]为u通过它的后代结点能访问到的最小的dfn(就是在u上面的结点)。
这里指通过多条树边,一条返祖边可以到达的最小的dfn(最上面的结点)。
④割边:对于每一个点u,如果low[v]小于等于dfn[u]说明v可以通过v的后代通到u的上面,那么割掉u没有用。
如果low[v]大于dfn[u],那么割掉(u,v)后u和v一定处于两个联通块中。
如图:
如图

⑤割点:也是同样的道理,但是当low[v]等于dfn[u]时u已经被删掉了,所以这种方案是割点。判断条件:low[v]>=dfn[u]

◇◆◇某些不可描述的细节◇◆◇
①根节点:它没有父亲,只要它有两个儿子就可以作为割点了。

https://vjudge.net/problem/HihoCoder-1183

代码(copy真的不好):

#include<set>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXN 20000
void G();
struct edge {    //结构体数组表示边
    int u,v;
};

bool operator<(edge a,edge b){    //给set定义优先级
    if(a.u==b.u) return a.v<b.v;
    return a.u<b.u;
}
//set具有判重功能,避免重复的点、边
set<int>Point;  //割点
set<edge>Bridge;    //割边
vector<int>G[MAXN+5];  //vector存入边
int n,m,cnt;
int fa[MAXN+5];
int dfn[MAXN+5],low[MAXN+5];
void G() {printf("GoodGame\n");}
void Dfs(int u) {
    int Children=0;
    dfn[u]=low[u]=++cnt;
    for(int i=0;i<G[u].size();i++) {
        int v=G[u][i];
        if(!dfn[v]) {
            Children++;           //存一下儿子的数量
            fa[v]=u;              //这里用fa把图转为了树
            Dfs(v);
            low[u]=min(low[u],low[v]);//在回溯的过程中更新low
            if(low[v]>dfn[u]) {      //判断割边
                edge k;
                k.u=min(u,v);
                k.v=max(u,v);
                Bridge.insert(k);//存入
            }
            if(!fa[u]&&Children>=2)//是根节点的情况
                Point.insert(u);
            else if(fa[u]&&low[v]>=dfn[u])//不是根节点的情况
                Point.insert(u);
        }
        else if(v!=fa[u]) {          //如果通过(u,v)到达u上面的节点
            low[u]=min(low[u],dfn[v]);//说明是返祖边,更新low
        }
    }
}

int main() {
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++) {
        int u,v;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);//无向图存双向
    }
    Dfs(1);
    bool first=1,flag=0;//以后为输出
    if(Point.empty())
        printf("Null");
    while(!Point.empty()) {
        int Ans=*Point.begin();
        if(first) {G();
            printf("%d",Ans);
            first=0;
        }
        else printf(" %d",Ans);
        Point.erase(Ans);
    }
    puts("");
    while(!Bridge.empty()) {
        edge Ans=*Bridge.begin();
        printf("%d %d\n",Ans.u,Ans.v);
        Bridge.erase(Ans);G();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值