无向图的割顶和割边模板题 P1637&&P1638

Description

割顶(割点或关节点):如果在图G中删去一个点v,连通分量数量增加,则称v为G的割顶。 本题问题:给出含n个节点m条边的连通图,请计算这个图的割顶集。

Input

第一行包含两个整数:n和m,分别表示图的节点数量(编号为1..n)和m条边。接下来的m行,每行包含两个整数u,v,表示一条边。

Output

第一行输出num,表示割点数目。第二行包含num个整数,每个整数表示一个割点的编号(由小到大输出)。

Hint

开800000。

Solution

定理 1:无向连通图的DFS树中,树根结点是两个或更多子树时,它才是割顶。
定理 2:无向连通图的DFS树中,非根结点i是割顶当且i存在一个儿j,使得j及其后代都没有返祖边连回到它的祖先(连回i不算)。
low[i]=min(low[i],dfn[j]);如果i的儿子的时间戳比i小,那么说明i的儿子j一定存在一条返祖边,那么i就一定不是割点。 low[i]=min(low[i],low[j]);如果j没有进去过,那么就把low[i]更新成他儿子的时间戳和他自己的最小值。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 200005
using namespace std;
struct Edge{
    int u;
    int v;
    int next;
    int id;
}edge[maxn];
int first[maxn],last[maxn],dfn[maxn],low[maxn],cut[maxn];
int node,dfn_TimeClock,n,m,x,y,cnt;
bool vis[maxn];
void addedge(int u,int v,int id){
    edge[++node]=(Edge){u,v,0,id};
    if(first[u]==0)first[u]=node;
    else edge[last[u]].next=node;
    last[u]=node;
}
void init(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        addedge(x,y,i);
        addedge(y,x,i);
    }
}
void tarjan_(int i,int fd){
    vis[i]=true;
    dfn[i]=low[i]=++dfn_TimeClock;
    int xxx=0;
    for(int p=first[i];p;p=edge[p].next){
        int j=edge[p].v,id=edge[p].id;
        if(vis[j]){
            if(dfn[j]<=dfn[i]&&id!=fd){
                low[i]=min(low[i],dfn[j]);
            }
            continue;
        }
        xxx++;
        tarjan_(j,id);
        low[i]=min(low[i],low[j]);
        if(low[j]>=dfn[i])cut[i]=1;
    }
    if(fd==0&&xxx==1)cut[i]=0;
}
int main(){
    init();
    for(int i=1;i<=n;i++){
        if(!vis[i])tarjan_(i,0);
    }
    for(int i=1;i<=n;i++){
        if(cut[i]==1)cnt++;
    }
    printf("%d\n",cnt);
    for(int i=1;i<=n;i++){
        if(cut[i])printf("%d ",i);
    }
    return 0;
}

Description

割边(桥):如果在图G中删去一条边e,连通分量数量增加,则称e为G的割边。 本题问题:给出含n个节点m条边的连通图,请计算这个图的割边。

Input

第一行包含两个整数: n和m,分别表示图的节点数量(编号为1..n)和m条边(可能有重边或自环)。接下来的m行,每行包含两个整数u,v,表示一条边。

Output

第一行输出num,num表示割边数目。接下来的num行,每行表示一条割边(u,v),要求,u由小到大,如果u相等则v由小到大。

Hint

开800000。

Solution

在求割点算法中,当由i及其后代走访完毕后,如果low[i]==dfn[i],则i的父边是割边(根结点没有父边)。因为当low[i]==dfn[i]时,说明i及其后代必须没有连接到i的祖先的返组边存在,即i及其后代必须通过i的父边才能与其他结点连通,所以i的父边是割边。
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#define maxn 800005
using namespace std;
struct Edge{
    int u;
    int v;
    int next;
    int id;
}edge[maxn];
struct anss{
    int leftt;
    int rightt;
    friend bool operator < (anss a,anss b){
        if(a.leftt<b.leftt)return true;
        else if(a.leftt==b.leftt)return a.rightt<b.rightt;
        else return false;
    }
}ans[maxn];
int first[maxn],last[maxn],dfn[maxn],low[maxn];
int node,n,m,x,y,dfn_TimeClock,cnt;
bool vis[maxn],mark[maxn];
void addedge(int u,int v,int id){
    edge[++node]=(Edge){u,v,0,id};
    if(first[u]==0)first[u]=node;
    else edge[last[u]].next=node;
    last[u]=node;
}
void init(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        addedge(x,y,i);
        addedge(y,x,i);
    }
}
void tarjan_T(int i,int fd){
    vis[i]=true;
    dfn[i]=low[i]=++dfn_TimeClock;
    for(int p=first[i];p;p=edge[p].next){
        int j=edge[p].v,id=edge[p].id;
        if(vis[j]){
            if(dfn[j]<=dfn[i]&&id!=fd){
                low[i]=min(low[i],dfn[j]);
            }
            continue;
        }
        tarjan_T(j,id);
        low[i]=min(low[i],low[j]);
    }
    if(low[i]==dfn[i]&&fd!=0)mark[fd]=true;
}
int main(){
    init();
    for(int i=1;i<=n;i++){
        if(!vis[i])tarjan_T(i,0);
    }
    for(int i=1;i<=n;i++){
        for(int p=first[i];p;p=edge[p].next){
            int j=edge[p].v;
            if(mark[edge[p].id]&&i<j){
                ans[++cnt]=(anss){i,j};
            }
        }
    }
    sort(ans+1,ans+cnt+1);
    printf("%d\n",cnt);
    for(int i=1;i<=cnt;i++){
        printf("%d %d\n",ans[i].leftt,ans[i].rightt);
    }
    return 0;
}

转载于:https://www.cnblogs.com/virtual-north-Illya/p/10045338.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值