图的连通性专题及模板

图的连通性

 

dfn[u]: 表示节点u的搜索优先级
low[u]: 表示节点u,通过其本身或其子节点能到达的最小有搜索优先级

low[u] = Min{
    1. dfn[u]         其本身搜索优先级
    2. Min{ low[v] }  其子节点v能到达的最小优先级
    3. Min( dfn[v] )  其通过回边(u,v),其中v为u的祖先节点,的优先级
}

 

一 无向图

1. 割点

又名关键点,若删除该点与其发出的边.则整个图不连通.

当前顶点u是一个关键点的充要条件是:

1. 若顶点U是根,则其必定包含两个以上的子节点. (因为若只有一个.删除了U之后,图仍然连通)

2. 若顶点U不是根, 则当 dfn[u] <= low[v] , (其中V是U的子孙节点). 因为V无法通过其本身或子孙到达U或者U更高级的点.所以删除U后,图不连通.

 

poj 1523 SPF

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 1010;

int edge[N][N];
int n, son;

int subnet[N], dfn[N], low[N], tmpdfn;
bool vis[N];

void init(){
    tmpdfn = 1; son = 0;
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(vis,0,sizeof(vis));
    memset(subnet,0,sizeof(subnet));
    low[1] = dfn[1] = 1; vis[1] = true;
}
void dfs(int u){
   // printf("u = %d\n", u );
    for(int v = 1; v <= n; v++)
    {
        if( edge[u][v] ){
            if( !vis[v] ){
                vis[v] = true;
                dfn[v] = low[v] = ++tmpdfn;
                dfs( v );
                low[u] = min( low[u], low[v] );
                if( low[v] >= dfn[u] ){
                    if( u != 1 ) subnet[u]++;
                    else son++;
                }
            }
            else low[u] = min( low[u], dfn[v] );
        }
    }
}

int main(){
  //  freopen("1.in","r",stdin);
    int Case = 0;
    while( 1 ){
        int u, v; n = 0;
        scanf("%d", &u);
        if( u == 0 ) break;
        memset(edge,0,sizeof(edge));
        scanf("%d",&v);
        if( u > n ) n = u;
        if( v > n ) n = v;
        edge[u][v] = edge[v][u] = 1;
        while(1){
            scanf("%d",&u);
            if( u == 0 ) break;
            scanf("%d",&v);
            if(u > n) n = u;
            if(v > n) n = v;
            edge[u][v] = edge[v][u] = 1;
        }
        if( Case ) puts("");
        printf("Network #%d\n", ++Case);
        init();
        dfs(1);
        if( son > 1 ) subnet[1] = son-1;
        bool find = false;
        for(int i = 1; i <= n; i++)
        {
            if( subnet[i] ){
                find = true;
                printf("  SPF node %d leaves %d subnets\n", i, subnet[i]+1);
            }
        }
        if( !find ) printf("  No SPF nodes\n");
    }
    return 0;
}
View Code

 

2. 割边(桥)

割边的判定条件:  dfn[u] < low[v] .    割边要注意重边

zoj 2588 burning bridge

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;

const int N = (int)1e4+10;

struct Edge{
    int v, tag, nxt;
}edge[N*20];
int head[N], idx;
int n, m;
int dfn[N], low[N], dep;
bool vis[N];

map< pair<int,int>,int  > mp;
vector<int> S;

void AddEdge(int u,int v)
{
    for(int i = head[u]; ~i; i = edge[i].nxt )
    if( edge[i].v == v ){
        edge[i].tag++;  return;
    }
    edge[idx].v = v; edge[idx].nxt = head[u]; edge[idx].tag = 0;
    head[u] = idx++;
}
void input(){
    memset( head, -1, sizeof(head));
    idx = 0;
    scanf("%d%d", &n,&m);
    mp.clear();
    for(int i = 0; i < m; i++)
    {
        int u, v;
        scanf("%d%d", &u,&v);
        mp[ make_pair(u,v) ] = mp[ make_pair(v,u) ] = i+1;
        AddEdge(u,v), AddEdge(v,u);
    }
}
void tarjan(int u,int pre)
{
    vis[u] = true;
    dfn[u] = low[u] = ++dep;
    for(int i = head[u]; ~i; i = edge[i].nxt )
    {
        int v = edge[i].v;
        if( v == pre ) continue;
        if( !vis[v] )
        {
            tarjan( v, u );
            low[u] = min( low[u], low[v] );
        }
        else low[u] = min( low[u], dfn[v] );
        if( (low[v] > dfn[u]) && !edge[i].tag ) S.push_back( mp[make_pair(u,v)] );
    }
}
void solve(){
    S.clear();

    memset(vis,0,sizeof(vis));
    dep = 0;
    tarjan(1,0);

    sort(S.begin(), S.end());
    int tot = (int)S.size();
    printf("%d\n", tot);
    for(int i = 0; i < tot; i++)
    printf(i == 0 ? "%d" :" %d", S[i] );
    if(tot) puts("");
}

int main(){
   /// freopen("1.in","r",stdin);
    int _;
    scanf("%d", &_);
    for(int Case = 0; Case < _; Case++)
    {
        if(Case) puts("");
        input();
        solve();
    }
    return 0;
}
View Code

 

 

3. 重连通分量(顶点重复,关键点)

具体做法是,通过一个栈将所有经过的边存储起来.当顶点U的子节点满足 dfn[u] <= low[v] 时,证明顶点U为一个关键点.则顺带找出了一个重连通分量.

要注意的是, 找的过程.所有的边只能被访问一次. 注意标记.

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;

const int N = 10101;
int n, m;

struct Edge{
    int u, v, nxt;
    Edge(){}
    Edge(int _u,int _v){
        u = _u; v = _v; nxt = 0;
    }
    int cmp( Edge &t ){
        return (u==t.u&&v==t.v)||(u==t.v&&v==t.u);
    }
    void print(){
        printf("%d->%d ", u, v );
    }
}edge[N<<4];
int head[N], idx;
int dfn[N], low[N], dep;
bool vis[N];
bool gao[N][N];

stack< Edge > S;

void AddEdge(int u,int v)
{
    edge[idx].u = u; edge[idx].v = v;
    edge[idx].nxt = head[u];
    head[u] = idx++;
}
void input(){
    memset(head,-1,sizeof(head));
    idx = 0;
    for(int i = 0; i < m; i++)
    {
        int u , v;
        scanf("%d%d", &u,&v);
        AddEdge(u,v); AddEdge(v,u);
    }
}
void tarjan(int u,int pre)
{
    dfn[u] = low[u] = ++dep;
    vis[u] = true;
    for(int i = head[u]; ~i; i = edge[i].nxt )
    {
        int v = edge[i].v;
        if( v == pre || gao[v][u] || gao[u][v] ) continue;

        gao[v][u] = gao[u][v] = true;    // 这里要注意标记边.回边也只能走一次.
        S.push( Edge(u,v) );
        if( !vis[v] ){
            tarjan(v,u);
            low[u] = min( low[u], low[v] );
        }
        else low[u] = min( low[u], dfn[v] ); // 回边
        if( low[v] >= dfn[u] ){ //删除顶点U,子节点V所在的子树将脱离.
                Edge t = Edge(u,v);
                while(1)
                {
                    Edge t1 = S.top(); S.pop();
                    t1.print();
                    if( t1.cmp( t ) ) break;
                }
                puts("");
        }
    }
}
void solve(){
    memset( vis, 0, sizeof(vis));
    memset( gao, 0, sizeof(gao));
    dep = 0;
    tarjan(1,0);
}
int main()
{
    freopen("1.txt","r",stdin);
    //while( scanf("%d%d", &n,&m) != EOF)
    scanf("%d%d", &n,&m);
    {
        input();
        solve();
    }
    return 0;
}

/*
7 9
1 2
1 3
1 6
1 7
2 3
2 4
2 5
4 5
6 7

*/
View Code

 

4. 边双连通分量

poj 3177 Redundant Paths

题意是使所有的顶点间都有两条路可达, 路径可共顶点但不可共边.

做法是. 将同一个边双连通分量的所有点收缩成一个点. 新构成的顶点形成一棵树. 

此时的任意两个顶点间都需要有至少两条相互"独立边", 则可以让成对的叶子节点连边.

所有总方案数 = (叶子节点数量+1)/2,    单个叶子节点也要连到其它节点去.

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 1010;

struct Node{
    int v, nxt;
}edge[N*100];
int head[N], idx;
int n, m;
int st[N];
int belong[N], dfn[N], low[N];
bool vis[N];
int bridge[N*10][2], nbridge, D[N];
int find(int x){ return x==st[x]?x:(st[x]=find(st[x]));}
void Union(int a,int b){
    int x = find(a), y = find(b);
    if( x != y ) st[x] = y;
}
void AddEdge(int u,int v)
{
    edge[idx].v = v;
    edge[idx].nxt = head[u];
    head[u] = idx++;
}
void input(){
    memset(head,-1,sizeof(head));
    idx = 0;
    for(int i = 0; i < m; i++)
    {
        int u, v;
        scanf("%d%d", &u,&v); u--, v--;
        AddEdge(u,v), AddEdge(v,u);
    }
}
void tarjan(int u,int pre,int dep){
    vis[u] = true;
    dfn[u] = low[u] = dep;
    for(int i = head[u]; ~i; i = edge[i].nxt)
    {
        int v = edge[i].v;
        if( v == pre ) continue;
        if( vis[v] ) low[u] = min( low[u], dfn[v] );
        else{
            tarjan( v,u,dep+1);
            low[u] = min( low[u], low[v] );
            if( dfn[u] >= low[v] ) Union(u,v);
            if( dfn[u] <  low[v] ){
                bridge[nbridge][0] = u; bridge[nbridge++][1] = v;
            }
        }
    }
}

int GetConnection(){
    for(int i = 0; i < n; i++) st[i] = i;
    nbridge = 0;
    memset(vis,0,sizeof(vis));
    memset(belong,-1,sizeof(belong));
    tarjan(0,-1,1);

    int conn = 0;
    for(int i = 0; i < n; i++)
    {
        int k = find(i);
        if( belong[k] == -1 ) belong[k] = conn++;
        belong[i] = belong[k];
    }
    return conn;
}

void solve(){
    int w = GetConnection();
    memset( D, 0, sizeof(D));
    for(int i = 0; i < nbridge; i++)
    {
        int u = bridge[i][0], v = bridge[i][1];
        D[ belong[u] ]++, D[ belong[v] ]++;
    }
    int cnt = 0;
    for(int i = 0; i < w; i++)
        if( D[i] == 1 ) cnt++;
    printf("%d\n", (cnt+1)/2 );
}
int main(){
    while( scanf("%d%d", &n,&m) != EOF)
    {
        input();
        solve();
    }
    return 0;
}
View Code

 

 

5. 点连通度

6. 边连通度

7. 无向图强连通分量缩点建树

hdu 4612 warm up

题目算法很好想. 添加一条边使"割边"最小. 则将 所有的强连通分量缩成一个点.然后构成一棵树.之后求出树上最长路,

这时候将最长路连起来,剩下的桥固然最小.

无向图强连通分量求法同 有向差不多.  可以选择 询问完所有子节点后利用 dfn[u]==low[u] 压栈合并. 也可以在里头用 low[v] <= dfn[u]

条件判定后用 并查集合并.

不管有向图,还是无向图. 要特别注意重边. 求割边主要是否要标记边.  这里有重边.就可以将两个顶点合并成强连通分量.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stack>
#include<algorithm>
using namespace std;
#pragma comment(linker, "/STACK:1024000000,1024000000")

const int N = 200010;
const int M = 1000010;

struct Edge{
    int v, nxt, flag;
}edge[M<<2];
int head[N], idx;
int n, m;

int dfn[N], low[N], dep, vis[N];
int id[N], conn;
bool instack[N];
int nbridge, bridge[M][2];
int maxlen;

stack<int> st;

void AddEdge(int u,int v){
    edge[idx].v = v; edge[idx].nxt = head[u];
    edge[idx].flag = 1; head[u] = idx++;
    edge[idx].v = u; edge[idx].nxt = head[v];
    edge[idx].flag = 1; head[v] = idx++;
}
void input(){
    memset(head,-1,sizeof(head));
    idx = 0;
    for(int i = 0; i < m; i++)
    {
        int u, v;
        scanf("%d%d", &u,&v);
        AddEdge(u,v);
    }
}

void tarjan(int u,int pre)  // 无向图,数据有回边.需要将其看做不同边.且边需要标记...
{
    vis[u] = true;
    dfn[u] = low[u] = ++dep;
    st.push(u); instack[u] = true;
    for(int i = head[u]; ~i; i = edge[i].nxt )
    {
        int v = edge[i].v;
        if( edge[i].flag == false ) continue;
        edge[i].flag = edge[i^1].flag = false;
        if( !vis[v] ){
            tarjan(v,u);
            low[u] = min( low[u], low[v] );
            if( (dfn[u] < low[v]) ){
                bridge[ nbridge ][0] = u;
                bridge[ nbridge++ ][1] = v;
            }
        }
        else if( instack[v] )
            low[u] = min( low[u], dfn[v] );
    }
    if( dfn[u] == low[u] )
    {
        int t;
        do{
            id[t=st.top()] = conn; st.pop();
            instack[t] = false;
        }while(t!=u);
        conn++;
    }
}
void GetConnect(){
    memset(vis,0,sizeof(vis));
    memset(instack,0,sizeof(instack));
    nbridge = 0;
    conn = 0; dep = 0;
    while( !st.empty() ) st.pop();
    for(int i = 1; i <= n; i++)
        if( !vis[i] ) tarjan(i,0);

    // Debug
   /* for(int i = 1; i <= n; i++)
        printf("dfn[%d] = %d, low[%d] = %d\n", i,dfn[i], i,low[i]);
    for(int i = 1; i <= n; i++)
        printf("id[%d] = %d\n", i, id[i] );*/
}
void ReGraph(){
    memset(head,-1,sizeof(head));
    idx = 0;
    for(int i = 0; i < nbridge; i++){
        int u = id[ bridge[i][0] ], v = id[ bridge[i][1] ];
       // printf("u = %d, v = %d\n", u, v );
        AddEdge(u,v);
    }
}

int dfs(int u,int pre)
{
    int tmp = 0;
    for(int i = head[u]; ~i; i = edge[i].nxt )
    {
        int v = edge[i].v;
        if( v == pre ) continue;

        int d = dfs(v,u);
        maxlen = max( maxlen, tmp+d );
        tmp = max(tmp,d);
    }
    return tmp+1;
}
void solve(){
    GetConnect();
    ReGraph();

    maxlen = 0;
    dfs(0,-1);

   // printf("maxlen = %d, conn = %d\n", maxlen, conn);
    printf("%d\n", conn-(maxlen+1) );
}
int main(){
    while( scanf("%d%d", &n,&m) != EOF  )
    {
        if(n + m == 0) break;
        input();
        solve();
    }
    return 0;
}
View Code

 

 

二 有向图

1. 有向图强连通分量

当 dfn[u] == dfn[v]时,以u为根的搜索子树上所有结点是一个强连通分量.

Tarjan( u )
{
    dfn[u] = low[u] = ++tmpdfn;
    Stack.push( u );
    for each (u,v) in E
    { 
        if( v is not visit )   // 此时当前边是生成树的边
        {
            tarjan( v );
            low[u] = min( low[u], low[v] )    
        }
        else if( v is in stack ) // 此时为回边
            low[u] = min( low[u], dfn[v] )
        // else 此时为 交叉边,其他连同分量上的边. 
    }
    if( dfn[u] == low[u] )
        repeat
            v = stack.pop;  将v腿栈,为该连通分量的一个顶点
            print v
        until( u == v )
}

 

poj 2676 Going from u to v or from v to u?

题目大意是给一个有向图,让判定该图是否任意两点可达.( u -> v or v->u )

算法是, 先求出所有强连通分量,然后进行缩点.构成新图. 然后对新图做拓扑排序, 若拓排后的序列相邻间都有边相连.则符合要求.

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<algorithm>
#include<stack>
#include<queue>
using namespace std;
#pragma comment(linker, "/STACK:1024000000,1024000000")

const int N = 1010;

int n, m;
struct Node{
    int v, nxt;
}edge[N*100];
int head[N], idx;

int dfn[N], low[N], id[N], dep;
bool vis[N], instack[N];
int graph[N][N];
int D[N], topo[N];
int conn;

stack<int> st;
void AddEdge(int u,int v)
{
    edge[idx].v = v; edge[idx].nxt = head[u];
    head[u] = idx++;
}
void input(){
    memset(head,-1,sizeof(head));
    idx = 0;
    for(int i = 0; i < m; i++)
    {
        int u, v;
        scanf("%d%d", &u,&v);
        AddEdge(u,v);
    }
}

void tarjan(int u,int pre)
{
    st.push(u); instack[u] = true;
    vis[u] = true; dfn[u] = low[u] = ++dep;
    for(int i = head[u]; ~i; i = edge[i].nxt )
    {
        int v = edge[i].v;
    //    if( v == pre ) continue;
        if( !vis[v] ){ // 生成树的边.
            tarjan(v,u);
            low[u] = min( low[u], low[v] );
        }
        else if( instack[v] ) // 在栈中.回边.
            low[u] = min( low[u], dfn[v] );
    }
    if( dfn[u] == low[u] ){   //顶点u为根的子树是一个强连同块
        int t;
        do{
            id[ t=st.top() ] = conn; st.pop();
            instack[t] = false; //low[t] = n;
        }while( t != u );
        conn++; //强连通分量增加
    }
}

int TopSort(){
    memset( D, 0, sizeof(D));
    memset( vis, 0, sizeof(vis));
    while( !st.empty() ) st.pop();
    for(int i = 0; i < conn; i++){
        for(int j = 0; j < conn; j++)
            if(i != j) D[i] += graph[j][i];
        if( D[i] == 0 ) st.push(i), vis[i] = true;
    }
  //  if( st.size() > 1 ) return 0;
    int tot = 0;
    while( !st.empty() )
    {
        int u = st.top(); st.pop(); vis[u] = false;
        topo[tot++] = u;
        //printf("TopSort:  u = %d\n", u );
        for(int v = 0; v < conn; v++)
        if( graph[u][v] )
        {
            if(u == v) continue;
            D[v] -= graph[u][v];
            if( D[v] == 0 && !vis[v] ) st.push(v), vis[v] = true;
        }
    }
    if( tot < conn ) return 0;
    return 1;
}
void solve(){
    conn = 0; dep = 0;
    while( !st.empty() ) st.pop();
    memset(vis,0,sizeof(vis));
    memset(instack,0,sizeof(instack));

    for(int i = 1; i <= n; i++)
        if( !vis[i] ) tarjan(i,0);
    memset( graph, 0, sizeof(graph));
    for(int u = 1; u <= n; u++)
    {
        for(int i = head[u]; ~i; i = edge[i].nxt )
        {
            int v = edge[i].v;
            graph[ id[u] ][ id[v] ] = 1;
        }
    }
    if( !TopSort() ) puts("No");
    else
    {
        //for(int i = 0; i < conn; i++)
       //     printf("%d ", topo[i] ); puts("");
        bool flag = true;
        for(int i = 0; i < conn-1; i++)
            if( graph[ topo[i] ][ topo[i+1] ] == 0 )
                flag = false;
        puts( flag ? "Yes" : "No");
    }

}
int main(){
    freopen("1.txt","r",stdin);
    int T;
    scanf("%d", &T);
    while( T-- )
    {
        scanf("%d%d", &n,&m);
        input();
        solve();
    }
    return 0;
}
View Code

 

poj 2186 Popular Cows

可以推出,若强连通分量中的一头牛A, 仰慕强连通分量外的一头牛B, 则连同分量内的其他牛都会仰慕牛B.

所以我们可以将 每个强连通分量 收缩成一个顶点. 然后因为要求 受所有牛仰慕的牛的数量. 所以这个顶点.

要么是一个 强连通缩点,要么就是单独一头牛. 此时应只有当前一个顶点 出度为0. 否则矛盾.

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<stack>
#include<algorithm>
using namespace std;

const int N = 10010;

struct Edge{
    int v, nxt;
}edge[N*10];
int head[N], idx;
int n, m;

int dfn[N], low[N], dep;
int id[N], conn, num[N];
bool vis[N], instack[N];
int D[N];
stack<int> st;

void AddEdge(int u,int v)
{
    edge[idx].v = v; edge[idx].nxt = head[u];
    head[u] = idx++;
}
void input()
{
    memset(head,-1,sizeof(head));
    idx = 0;
    for(int i = 0; i < m; i++)
    {
        int u, v;
        scanf("%d%d", &u,&v);
        AddEdge(u,v);
    }
}
void tarjan(int u,int pre){
    st.push(u); instack[u] = true;
    vis[u] = true;
    dfn[u] = low[u] = ++dep;
    for(int i = head[u]; ~i; i = edge[i].nxt )
    {
        int v = edge[i].v;

        if( !vis[v] ){
            tarjan(v,u);
            low[u] = min( low[u], low[v] );
        }
        else if( instack[v] )
            low[u] = min( low[u], dfn[v] );
    }
    if( dfn[u] == low[u] )
    {
        int t;
        do{
            id[ t=st.top() ] = conn; st.pop();
            instack[t] = false;
            num[ conn ]++;   // 该连通分量节点数量
        }while( t != u );
        conn++;
    }
}
int GetConnect(){
    conn = 0; dep = 0;
    memset(vis,0,sizeof(vis));
    memset(instack,0,sizeof(instack));
    for(int i = 1; i <= n; i++)
        if( !vis[i] ) tarjan(i,0);

   /* for(int i = 1; i <= n; i++){
        printf("dfn[%d] = %d, low[%d] = %d\n", i, dfn[i], i, low[i] );
        printf("id[%d] = %d\n", i, id[i] );
    }*/
    return conn;
}
void solve()
{
    GetConnect();

    for(int u = 1; u <= n; u++)
    {
        for(int i = head[u]; ~i; i = edge[i].nxt )
        {
            int v = edge[i].v;
            if( id[u] != id[v] ) D[ id[u] ]++;
        }
    }
    int ts = 0, rs = 0;
    for(int i = 0; i < conn; i++)
    {
        if( D[i] == 0 ) ts++, rs = i;
    }
    if( ts == 1 ) printf("%d\n", num[rs] );
    else puts("0");

}
int main()
{
    //freopen("1.txt","r",stdin);
    while( scanf("%d%d", &n,&m) != EOF )
    {
        input();
        solve();
    }
    return 0;
}
View Code

 

 

 

posted @ 2013-07-26 13:49  yefeng1627  阅读(1062)  评论(0编辑  收藏  举报

Launch CodeCogs Equation Editor