图论分支-Tarjan初步-边双联通分量

本来应该先说强连通分量,但是有一定的分配,所以这个在下一篇博客将会见到。

这个本想连点连通分量一起讲,但是量有点大,所以我就分两步讲。

我们先看定义

图论分支-Tarjan初步-边双联通分量

再来看看图解

图论分支-Tarjan初步-边双联通分量

图论分支-Tarjan初步-边双联通分量

很容易就能发现,只要将割边断掉,然后剩下的连通块就是我们的边双,那么我们的代码就可以yy出来了,先跑一遍Tarjan求割点,然后在去跑dfs,将每一个边双染色,那么就可以了,而染色操作,以便于我们后面好缩点。

我们来看模板

void Tarjan(int x,int fa){
    low[x]=dfn[x]=++t;
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(!dfn[y]){
            Tarjan(y,i);
            low[x]=min(low[y],low[x]);
            if(low[y]>dfn[x]) 
                edge[i].flag=edge[(i^1)].flag=1;//寻找割边 ,并标记 
        }else if((i^1)!=fa){
            low[x]=min(low[x],dfn[y]);
        }
    }
}

void dfs(int x){
    col[x]=tot;//染色 
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(!col[y]&&!edge[i].flag)//不能是走过的点和割边 
            dfs(y);//遍历 
    }
}

//下面插入主函数中

for(int i=1;i<=n;i++){
        if(!dfn[i]) Tarjan(i,-1);
    }
for(int i=1;i<=n;i++){
    if(!col[i]) ++tot,dfs(i);
}

大家仔细琢磨一下,应该就能懂他的思想了。

 

然后我们来看例题:

图论分支-Tarjan初步-边双联通分量

这道题很显然是将这个图变为边双。在同一个边双连通分量中,任意两点都有至少两条独立路可达,所以同一个边双连通分量里的所有点可以看做同一个点。 这时就要用到我们的染色缩点,缩点请仔细思考怎么做。

缩点后,新图是一棵树,树的边就是原无向图的桥。 现在问题转化为:在树中至少添加多少条边能使图变为双连通图。

 此时我们就能想出之前我们学过的入度和出度;

那么,只要将度为一的点连边即可,那么此时我们的公式就是添加边数=(树中度为1的节点数+1)/2;

具体做法就是:

首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。 然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。

这样就解决了,来看

Code

 

#include<bits/stdc++.h>
#define maxn 5007
#define M 20007
using namespace std;
int n,m,t,head[maxn],cent=1,low[maxn],dfn[maxn],vis[maxn];
int tot,col[maxn],out[maxn],ans,in[maxn],ol;
struct node{
    int next,to,flag,from;
}edge[M<<1];

void add(int u,int v){
    edge[++cent]=(node){head[u],v,0,u};head[u]=cent;
}

void Tarjan(int x,int fa){
    low[x]=dfn[x]=++t;
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(!dfn[y]){
            Tarjan(y,i);
            low[x]=min(low[y],low[x]);
            if(low[y]>dfn[x]) 
                edge[i].flag=edge[(i^1)].flag=1;//寻找割边 ,并标记 
        }else if((i^1)!=fa){
            low[x]=min(low[x],dfn[y]);
        }
    }
}

void dfs(int x){
    col[x]=tot;//染色 
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(!col[y]&&!edge[i].flag)//不能是走过的点和割边 
            dfs(y);//遍历 
    }
}

void make_dfs(int x,int fa){
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(y==fa) continue;
        out[y]++,in[x]++;//计算入度出度 
        make_dfs(y,x);
    }
}

int main(){
//    freopen("rpaths.in","r",stdin);
//    freopen("rpaths.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1,a,b;i<=m;i++){
        scanf("%d%d",&a,&b);
        if(a==b) continue;
        add(a,b),add(b,a);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]) Tarjan(i,-1);
    }
    for(int i=1;i<=n;i++){
        if(!col[i]) ++tot,dfs(i);
    }
    memset(head,0,sizeof(head));cent=1;//预处理 
    for(int i=2;i<=2*m+1;i++){
        if(edge[i].flag){
            add(col[edge[i].from],col[edge[i].to]);
        }//重点!!!染色缩点建树 
    }
    make_dfs(1,-1);//寻找度 
    for(int i=1;i<=tot;++i){
        if((out[i]==1&&in[i]==0)||(in[i]==1&&!out[i])){
            ans++;//统计度为一的点 
        }
    }
    printf("%d\n",(ans+1)/2);
}

下一道也很简单

图论分支-Tarjan初步-边双联通分量

我们只要缩点,然后求距离就可以了,LCA求距离都懂的吧?(好像又没讲,下次下次);

我的错,没说LCA,在讲完LCA后再来看这道吧,但看完后就懂代码了:

Code

#include<bits/stdc++.h>
#define maxn 100007
using namespace std;
int n,m,cent=1,head[maxn],low[maxn],dfn[maxn],col[maxn],t,fa[maxn][30];
int tot,dep[maxn],q;
struct node{
    int next,to,from,flag;
}edge[maxn<<2];

void add(int u,int v){
    edge[++cent]=(node){head[u],v,u,0};head[u]=cent;
}

void make_dfs(int x,int dy){
    dep[x]=dy;
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(fa[x][0]==y) continue;
        fa[y][0]=x;
        make_dfs(y,dy+1);
    }
}

void Init(){
    fa[1][0]=-1;
    make_dfs(1,0);
    for(int i=1;i<=25;i++){
        for(int j=1;j<=tot;j++){
            if(fa[j][i-1]<0) fa[j][i]=-1;
            else fa[j][i]=fa[fa[j][i-1]][i-1];
        }
    }
    return ;
}

int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=0,d=dep[x]-dep[y];d;d>>=1,i++){
        if(d&1) x=fa[x][i];
    }
    if(x==y) return x;
    for(int i=25;i>=0;i--){
        if(fa[x][i]!=fa[y][i]){
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}

void Tarjan(int x,int f){
    low[x]=dfn[x]=++t;
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(!dfn[y]){
            Tarjan(y,i);
            low[x]=min(low[x],low[y]);
            if(low[y]>dfn[x]) 
                edge[i].flag=edge[(i^1)].flag=1;
        }else if((i^1)!=f){
            low[x]=min(low[x],dfn[y]);
        }
    }
}

void dfs(int x){
    col[x]=tot;
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(!col[y]&&!edge[i].flag)
            dfs(y);
    }
}

int main(){
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1,a,b;i<=m;i++){
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]) Tarjan(i,-1);
    }
    for(int i=1;i<=n;i++){
        if(!col[i]) ++tot,dfs(i);
    }
    memset(head,0,sizeof(head));cent=1;
    for(int i=2;i<=2*m+1;i++){
        if(edge[i].flag){
            add(col[edge[i].from],col[edge[i].to]);
        }//缩点套路,一定要会 
    }
    Init();//LCA初始化 
    scanf("%d",&q);
    for(int i=1,a,b;i<=q;i++){
        scanf("%d%d",&a,&b);
        printf("%d\n",dep[col[a]]+dep[col[b]]-2*dep[lca(col[a],col[b])]);//求距离 
    }
}

这就差不多结束了,自己可以在找些题,要

深刻理解缩点的重要性

 

图论分支-Tarjan初步-边双联通分量

上一篇:.NET ---- B/S的特点,不接收js赋值


下一篇:手机端自适应显示样式设置(两种方式)