POJ 3352&&3177 (割边 && 边双连通分量)

题目:加几条边才能使原图变成边双连通分量。 [求割边]对图深度优先搜索,定义DFS(u)为u在搜索树(以下简称为树)中被遍历到的次序号。定义Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点。根据定义,则有:Low(u)=Min {DFS(u),DFS(v)|(u,v)为后向边(等价于DFS(v)<DFS(u)且v不为u的父亲节点),Low(v)|(u,v)为树枝边} [条件]一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v) [求点双连通分支]只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。 [注]重边对求割边和边连通分量有影响,此题无重边,重边对求割边和边连通分量有影响,重边处理方法是在DFS时标记每条边及其反向边是否被访问过(即按边访问),而不是判断顶点(按点访问)。 POJ 3352(无重边):
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define MID(x,y) ((x+y)>>1)
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;

const int MAXV = 5005;
const int MAXE = 20005;
struct node{
    int u, v;
    int next;
    int opp;        //把一个无向边拆成两个有向边,对应反向边标号
}arc[MAXE];
int cnt, head[MAXV];
void init(){
    cnt = 0;
    mem(head, -1);
    return ;
}
void add(int u, int v){
    arc[cnt].u = u;
    arc[cnt].v = v;
    arc[cnt].next = head[u];
    arc[cnt].opp = cnt + 1;
    head[u] = cnt ++;
    arc[cnt].u = v;
    arc[cnt].v = u;
    arc[cnt].next = head[v];
    arc[cnt].opp = cnt - 1;
    head[v] = cnt ++;
    return ;
}
int id, dfn[MAXV], low[MAXV];
int bridge[MAXE];                //标记该边是不是桥
void tarjan(int u, int father){
    dfn[u] = low[u] = ++id;
    for (int i = head[u]; i != -1; i = arc[i].next){
        int v = arc[i].v;
        if (v == father)    continue;
        //if (dfn[v] < dfn[u]){
            if (!dfn[v]){
                tarjan(v, u);
                low[u] = min(low[u], low[v]);
                if (dfn[u] < low[v]){
                    bridge[i] = 1;
                    bridge[arc[i].opp] = 1;
                }
            }
            else{
                low[u] = min(low[u], dfn[v]);
            }
        //}
    }
}
int bcc_num;
bool vis[MAXV];
bool vis_arc[MAXE];             //一条边无向边(两个有向边)只访问一次,
int bcc[MAXV];                  //标记点属于哪个边双连通分支
int deg[MAXV];                  //缩点后的新图节点度数,便于计算叶子结点数
void fill(int u){
    vis[u] = 1;
    bcc[u] = bcc_num;
    //cout  << u << " " << bcc[u] << endl;
    for (int i = head[u]; i != -1; i = arc[i].next){
        if (bridge[i])  continue;
        int v = arc[i].v;
        if (!vis[v])
            fill(v);
    }
}
int find_bcc(int n){
    mem(vis, 0);
    mem(deg, 0);
    //确定每个点所属边双联通分量
    for (int i = 1; i <= n; i ++){
        if (!vis[i]){
            ++ bcc_num;
            fill(i);
        }
    }
    mem(vis_arc, 0);
    //计算还需连几条边才能构成双联通图
    for (int i = 0; i < cnt; i ++){
        if (!vis_arc[i]){
        //if(bridge[i]){
            vis_arc[i] = vis_arc[arc[i].opp] = 1;
            int u = arc[i].u;
            int v = arc[i].v;
            if (bcc[u] != bcc[v]){
                deg[bcc[u]] ++;
                deg[bcc[v]] ++;
            }
        }
    }
    int res = 0;
    for (int i = 1; i <= bcc_num; i ++){
        if (deg[i] == 1)
            res ++;
    }
    return (res+1)/2;
}
void solve(int n){
    id = bcc_num =0;
    mem(dfn, 0);
    mem(low, 0);
    mem(bridge, 0);
    for (int i = 1; i <= n; i ++){
        if (!dfn[i])
            tarjan(1, 0);
    }
    printf("%d\n", find_bcc(n));
    return ;
}
int main(){
    int F,R;
    while(scanf("%d %d", &F, &R) != EOF){
        init();
        for (int i = 0; i < R; i ++){
            int u, v;
            scanf("%d %d", &u, &v);
            add(u, v);
        }
        solve(F);
    }
	return 0;
}
  POJ 3177 (有重边):
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define MID(x,y) ((x+y)>>1)
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;

const int MAXV = 5005;
const int MAXE = 20005;
struct node{
    int u, v;
    int next;
    int opp;        //把一个无向边拆成两个有向边,对应反向边标号
}arc[MAXE];
int cnt, head[MAXV];
void init(){
    cnt = 0;
    mem(head, -1);
    return ;
}
void add(int u, int v){
    arc[cnt].u = u;
    arc[cnt].v = v;
    arc[cnt].next = head[u];
    arc[cnt].opp = cnt + 1;
    head[u] = cnt ++;
    arc[cnt].u = v;
    arc[cnt].v = u;
    arc[cnt].next = head[v];
    arc[cnt].opp = cnt - 1;
    head[v] = cnt ++;
    return ;
}
int id, dfn[MAXV], low[MAXV];
int bridge[MAXE];                //标记该边是不是桥
bool vis_arc[MAXE];             //一条边无向边(两个有向边)只访问一次,
void tarjan(int u, int father){
    dfn[u] = low[u] = ++id;
    for (int i = head[u]; i != -1; i = arc[i].next){
        int v = arc[i].v;
        if (vis_arc[i]) continue;
        vis_arc[i] = vis_arc[arc[i].opp] = 1;
        if (!dfn[v]){
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if (dfn[u] < low[v]){
                bridge[i] = bridge[arc[i].opp] = 1;
            }
        }
        else{
            low[u] = min(low[u], dfn[v]);
        }
    }
}
int bcc_num;
bool vis[MAXV];
int bcc[MAXV];                  //标记点属于哪个边双连通分支
int deg[MAXV];                  //缩点后的新图节点度数,便于计算叶子结点数
void fill(int u){
    vis[u] = 1;
    bcc[u] = bcc_num;
    //cout << u << " " << bcc[u] << endl;
    for (int i = head[u]; i != -1; i = arc[i].next){
        if (bridge[i])  continue;
        int v = arc[i].v;
        if (!vis[v])
            fill(v);
    }
}
int find_bcc(int n){
    mem(vis, 0);
    mem(deg, 0);
    //确定每个点所属边双联通分量
    for (int i = 1; i <= n; i ++){
        if (!vis[i]){
            ++ bcc_num;
            fill(i);
        }
    }
    mem(vis_arc, 0);
    //计算还需连几条边才能构成双联通图
    for (int i = 0; i < cnt; i ++){
        if (!vis_arc[i]){
            vis_arc[i] = vis_arc[arc[i].opp] = 1;
            int u = arc[i].u;
            int v = arc[i].v;
            if (bcc[u] != bcc[v]){
                deg[bcc[u]] ++;
                deg[bcc[v]] ++;
            }
        }
    }
    int res = 0;
    for (int i = 1; i <= bcc_num; i ++){
        if (deg[i] == 1)
            res ++;
    }
    return (res+1)/2;
}
void solve(int n){
    id = bcc_num =0;
    mem(dfn, 0);
    mem(low, 0);
    mem(bridge, 0);
    mem(vis_arc, 0);
    for (int i = 1; i <= n; i ++){
        if (!dfn[i])
            tarjan(1, 0);
    }
    printf("%d\n", find_bcc(n));
    return ;
}
int main(){
    int F,R;
    while(scanf("%d %d", &F, &R) != EOF){
        init();
        for (int i = 0; i < R; i ++){
            int u, v;
            scanf("%d %d", &u, &v);
            add(u, v);
        }
        solve(F);
    }
	return 0;
}
举杯独醉,饮罢飞雪,茫然又一年岁。 ------AbandonZHANG
原文地址:https://www.cnblogs.com/AbandonZHANG/p/4114027.html